blob: 124b6229697ae9a3be2b3eaffa77058f3c2de6d2 [file] [log] [blame]
swissChili729acd52024-03-05 11:52:45 -05001#define VERSION "2.23"
2/*
3 * units, a program for units conversion
4 * Copyright (C) 1996, 1997, 1999, 2000-2007, 2009, 2011-2020, 2022, 2024
5 * Free Software Foundation, Inc
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301 USA
21 *
22 * This program was written by Adrian Mariano (adrianm@gnu.org)
23 */
24
25#define LICENSE "\
26Copyright (C) 2024 Free Software Foundation, Inc.\n\
27GNU Units comes with ABSOLUTELY NO WARRANTY.\n\
28You may redistribute copies of GNU Units\n\
29under the terms of the GNU General Public License."
30
31#define _XOPEN_SOURCE 600
32
33#if defined (_WIN32) && defined (_MSC_VER)
34# include <windows.h>
35# include <winbase.h>
36#endif
37#if defined (_WIN32) && defined (HAVE_MKS_TOOLKIT)
38# include <sys/types.h>
39#endif
40# include <sys/stat.h>
41
42#include <ctype.h>
43#include <float.h>
44
45#include <stdio.h>
46#include <signal.h>
47#include <stdarg.h>
48#include <time.h>
49
50#if defined (_WIN32) && defined (_MSC_VER)
51# include <io.h>
52# define fileno _fileno
53# define isatty _isatty
54# define stat _stat
55#endif
56
57#ifdef HAVE_IOCTL
58# include <sys/ioctl.h>
59# include <fcntl.h>
60#endif
61
62#ifndef NO_SETLOCALE
63# include<locale.h>
64#endif
65
66#ifdef SUPPORT_UTF8
67/* Apparently this define is needed to get wcswidth() prototype */
68# include <wchar.h>
69# include <langinfo.h>
70# define UTF8VERSTR "with utf8"
71#else
72# define UTF8VERSTR "without utf8"
73#endif
74
75#ifdef READLINE
76# define RVERSTR "with readline"
77# include <readline/readline.h>
78# include <readline/history.h>
79# define HISTORY_FILE ".units_history"
80#else
81# define RVERSTR "without readline"
82#endif
83
84#include "getopt.h"
85#include "units.h"
86
87#ifndef UNITSFILE
88# define UNITSFILE "definitions.units"
89#endif
90
91#ifndef LOCALEMAP
92# define LOCALEMAP "locale_map.txt"
93#endif
94
95#ifndef DATADIR
96# ifdef _WIN32
97# define DATADIR "..\\share\\units"
98# else
99# define DATADIR "../share/units"
100# endif
101#endif
102
103#if defined (_WIN32) && defined (_MSC_VER)
104# include <direct.h>
105# define getcwd _getcwd
106#else
107# include <unistd.h>
108#endif
109
110#ifdef _WIN32
111# define EXE_EXT ".exe"
112# define PATHSEP ';'
113# define DIRSEP '\\'
114# define DIRSEPSTR "\\" /* for building pathnames */
115#else
116# define EXE_EXT ""
117# define PATHSEP ':'
118# define DIRSEP '/'
119# define DIRSEPSTR "/" /* for building pathnames */
120#endif
121
122#define PRIMITIVECHAR '!' /* Character that marks irreducible units */
123#define COMMENTCHAR '#' /* Comments marked by this character */
124#define COMMANDCHAR '!' /* Unit database commands marked with this */
125#define UNITSEPCHAR ';' /* Separator for unit lists. Include this */
126 /* char in rl_basic_word_break_characters */
127 /* and in nonunitchars defined in parse.y */
128#define FUNCSEPCHAR ';' /* Separates forward and inverse definitions */
129#define REDEFCHAR '+' /* Mark unit as redefinition to suppress warning message */
130#ifdef _WIN32
131# define DEFAULTPAGER "more" /* Default pager for Windows */
132#else
133# define DEFAULTPAGER "/usr/bin/pager" /* Default pager for Unix */
134#endif
135#define DEFAULTLOCALE "en_US" /* Default locale */
136#define MAXINCLUDE 5 /* Max depth of include files */
137#define MAXFILES 25 /* Max number of units files on command line */
138#define NODIM "!dimensionless" /* Marks dimensionless primitive units, such */
139 /* as the radian, which are ignored when */
140 /* doing unit comparisons */
141#define NOPOINT -1 /* suppress display of pointer in processunit*/
142#define NOERRMSG -2 /* no error messages in checkunitlist() */
143#define ERRMSG -3
144#define SHOWFILES -4
145
146#define MAXHISTORYFILE 5000 /* max length of history file for readline */
147
148#define MAXPRODUCTREDUCTIONS 1000 /* If we make this many reductions, declare */
149 /* a circular reference */
150
151/* values for output number format */
152#define BASE_FORMATS "gGeEf" /* printf() format types recognized pre-C99 */
153#define DEFAULTPRECISION 8 /* default significant digits for printf() */
154#define DEFAULTTYPE 'g' /* default number format type for printf() */
155#define MAXPRECISION DBL_DIG /* maximum number precision for printf() */
156
157#define HOME_UNITS_ENV "MYUNITSFILE" /* Personal units file environment var */
158
159#define NOERROR_KEYWORD "noerror " /* The trailing space is important */
160#define CO_NOARG -1
161
162#define HELPCOMMAND "help" /* Command to request help at prompt */
163#define SEARCHCOMMAND "search" /* Command to request text search of units */
164#define UNITMATCH "?" /* Command to request conformable units */
165char *exit_commands[]={"quit","exit",0};
166char *all_commands[]={"quit","exit",HELPCOMMAND,SEARCHCOMMAND,UNITMATCH,0};
167
168/* Key words for function definitions */
169struct {
170 char *word;
171 char delimit;
172 int checkopen; /* allow open intervals with parentheses */
173} fnkeywords[]={ {"units=", FUNCSEPCHAR, 0},
174 {"domain=", ',', 1},
175 {"range=", ',',1},
176 {NOERROR_KEYWORD, ' ',CO_NOARG},
177 {0,0}};
178#define FN_UNITS 0
179#define FN_DOMAIN 1
180#define FN_RANGE 2
181#define FN_NOERROR 3
182
183char *builtins[] = {"sin", "cos", "tan","ln", "log", "exp",
184 "acos", "atan", "asin", "sqrt", "cuberoot", "per",
185 "sinh", "cosh", "tanh", "asinh", "atanh", "acosh", 0};
186
187struct {
188 char *format; /* printf() format specification for numeric output */
189 int width; /* printf() width from format */
190 int precision; /* printf() precision from format */
191 char type; /* printf() type from format */
192} num_format;
193
194
195struct { /* Program command line option flags */
196 int
197 interactive,
198 unitlists, /* Perform unit list output if set */
199 oneline, /* Suppresses the second line of output */
200 quiet, /* Supress prompting (-q option) */
201 round, /* Round the last of unit list output to nearest integer */
202 showconformable, /* */
203 showfactor, /* */
204 strictconvert, /* Strict conversion (disables reciprocals) */
205 unitcheck, /* Enable unit checking: 1 for regular check, 2 for verbose*/
206 verbose, /* Flag for output verbosity */
207 readline; /* Using readline library? */
208} flags;
209
210
211#define UTF8MARKER "\xEF\xBB\xBF" /* UTF-8 marker inserted by some Windows */
212 /* programs at start of a UTF-8 file */
213
214struct parseflag parserflags; /* parser options */
215
216
217char *homeunitsfile = ".units"; /* Units filename in home directory */
218char *homedir = NULL; /* User's home direcotry */
219char *homedir_error = NULL; /* Error message for home directory search */
220char *pager; /* Actual pager (from PAGER environment var) */
221char *mylocale; /* Locale in effect (from LC_CTYPE or LANG) */
222int utf8mode; /* Activate UTF8 support */
223char *powerstring = "^"; /* Exponent character used in output */
224char *unitsfiles[MAXFILES+1]; /* Null terminated list of units file names */
225char *logfilename=NULL; /* Filename for logging */
226FILE *logfile=NULL; /* File for logging */
227char *promptprefix=NULL; /* Prefix added to prompt */
228char *progname; /* Used in error messages */
229char *fullprogname; /* Full path of program; printversion() uses */
230char *progdir; /* Used to find supporting files */
231char *datadir; /* Used to find supporting files */
232char *deftext="";/* Output text when printing definition */
233char *digits = "0123456789.,";
234
235
236#define QUERYHAVE "You have: " /* Prompt text for units to convert from */
237#define QUERYWANT "You want: " /* Prompt text for units to convert to */
238
239#define LOGFROM "From: " /* tag for log file */
240#define LOGTO "To: " /* tag for log file */
241
242
243#define HASHSIZE 101 /* Values from K&R 2nd ed., Sect. 6.6 */
244#define HASHNUMBER 31
245
246#define SIMPLEHASHSIZE 128
247#define simplehash(str) (*(str) & 127) /* "hash" value for prefixes */
248
249#define POINTER "^" /* pointer to location of a parse error */
250
251#define ERRNUMFMT "%.8g" /* Numerical format for certain error messages */
252
253char *errormsg[]={
254 /* 0 */ "Successful completion",
255 /* 1 */ "Parse error",
256 /* 2 */ "Product overflow",
257 /* 3 */ "Unit reduction error (bad unit definition)",
258 /* 4 */ "Circular unit definition",
259 /* 5 */ "Invalid sum or difference of non-conformable units",
260 /* 6 */ "Unit not dimensionless",
261 /* 7 */ "Unit not a root",
262 /* 8 */ "Unknown unit",
263 /* 9 */ "Bad argument",
264 /* 10 */ "Weird nonlinear unit type (bug in program)",
265 /* 11 */ "Function argument has wrong dimension",
266 /* 12 */ "Argument of function outside domain",
267 /* 13 */ "Nonlinear unit definition has unit error",
268 /* 14 */ "No inverse defined",
269 /* 15 */ "Parser memory overflow (recursive function definition?)",
270 /* 16 */ "Argument wrong dimension or bad nonlinear unit definition",
271 /* 17 */ "Cannot open units file",
272 /* 18 */ "Units file contains errors",
273 /* 19 */ "Memory allocation error",
274 /* 20 */ "Malformed number",
275 /* 21 */ "Unit name ends with a digit other than 0 or 1 without preceding '_'",
276 /* 22 */ "No previous result; '_' not set",
277 /* 23 */ "Base unit not dimensionless; rational exponent required",
278 /* 24 */ "Base unit not a root",
279 /* 25 */ "Exponent not dimensionless",
280 /* 26 */ "Unknown function name",
281 /* 27 */ "Overflow: number too large",
282 /* 28 */ "Underflow: number too small"
283};
284
285char *invalid_utf8 = "invalid/nonprinting UTF-8";
286
287char *irreducible=0; /* Name of last irreducible unit */
288
289
290/* Hash table for unit definitions. */
291
292struct unitlist {
293 char *name; /* unit name */
294 char *value; /* unit value */
295 int linenumber; /* line in units data file where defined */
296 char *file; /* file where defined */
297 struct unitlist *next; /* next item in list */
298} *utab[HASHSIZE];
299
300
301/* Table for prefix definitions. */
302
303struct prefixlist {
304 int len; /* length of name string */
305 char *name; /* prefix name */
306 char *value; /* prefix value */
307 int linenumber; /* line in units data file where defined */
308 char *file; /* file where defined */
309 struct prefixlist *next; /* next item in list */
310} *ptab[SIMPLEHASHSIZE];
311
312
313struct wantalias {
314 char *name;
315 char *definition;
316 struct wantalias *next;
317 int linenumber;
318 char *file;
319};
320
321struct wantalias *firstalias = 0;
322struct wantalias **aliaslistend = &firstalias; /* Next list entry goes here */
323
324/* Table for function definitions */
325
326struct func *ftab[SIMPLEHASHSIZE];
327
328/*
329 Used for passing parameters to the parser when we are in the process
330 of parsing a unit function. If function_parameter is non-nil, then
331 whenever the text in function_parameter appears in a unit expression
332 it is replaced by the unit value stored in parameter_value.
333*/
334
335char *function_parameter = 0;
336struct unittype *parameter_value = 0;
337
338/* Stores the last result value for replacement with '_' */
339
340int lastunitset = 0;
341struct unittype lastunit;
342
343char *NULLUNIT = ""; /* Used for units that are canceled during reduction */
344
345#define startswith(string, prefix) (!strncmp(string, prefix, strlen(prefix)))
346#define lastchar(string) (*((string)+strlen(string)-1))
347#define emptystr(string) (*(string)==0)
348#define nonempty(list) ((list) && *(list))
349
350
351#ifdef READLINE
352
353char *historyfile; /* Filename for readline history */
354int init_history_length; /* Length of history read from the history file*/
355int init_history_base;
356
357void
358save_history(void)
359{
360 int newentries;
361 int err;
362
363 newentries = history_length-init_history_length;
364 if (history_max_entries > 0){
365 newentries += history_base - init_history_base;
366 if (newentries > history_max_entries)
367 newentries = history_max_entries;
368 }
369
370 err = append_history(newentries,historyfile);
371 if (err){
372 if (err == ENOENT)
373 err = write_history(historyfile);
374 if (err) {
375 printf("Unable to write history to '%s': %s\n",historyfile,strerror(err));
376 return;
377 }
378 }
379 history_truncate_file(historyfile,MAXHISTORYFILE);
380}
381#endif
382
383
384/* Increases the buffer by BUFGROW bytes and leaves the new pointer in buf
385 and the new buffer size in bufsize. */
386
387#define BUFGROW 100
388
389void
390growbuffer(char **buf, int *bufsize)
391{
392 int usemalloc;
393
394 usemalloc = !*buf || !*bufsize;
395 *bufsize += BUFGROW;
396 if (usemalloc)
397 *buf = malloc(*bufsize);
398 else
399 *buf = realloc(*buf,*bufsize);
400 if (!*buf){
401 fprintf(stderr, "%s: memory allocation error (growbuffer)\n",progname);
402 exit(EXIT_FAILURE);
403 }
404}
405
406
407FILE *
408openfile(char *file,char *mode)
409{
410 FILE *fileptr;
411
412 struct stat statbuf;
413 if (stat(file, &statbuf)==0 && statbuf.st_mode & S_IFDIR){
414 errno=EISDIR;
415 return NULL;
416 }
417 fileptr = fopen(file,mode);
418 return fileptr;
419}
420
421
422
423void
424logprintf(const char *format, ...)
425{
426 va_list args;
427
428 va_start(args, format);
429 vprintf(format, args);
430 va_end(args);
431 if (logfile) {
432 va_start(args, format);
433 vfprintf(logfile, format, args);
434 va_end(args);
435 }
436}
437
438void
439logputchar(char c)
440{
441 putchar(c);
442 if (logfile) fputc(c, logfile);
443}
444
445void
446logputs(const char *s)
447{
448 fputs(s, stdout);
449 if (logfile) fputs(s, logfile);
450}
451
452
453/* Look for a subscript in the input string. A subscript starts with
454 '_' and is followed by a sequence of only digits (matching the
455 regexp "_[0-9]+"). The function returns 1 if it finds a subscript
456 and zero otherwise. Note that it returns 1 for an input that is
457 entirely subscript, with the '_' appearing in the first position. */
458
459int
460hassubscript(const char *str)
461{
462 const char *ptr = &lastchar(str);
463 while (ptr>str){
464 if (!strchr(digits, *ptr))
465 return 0;
466 ptr--;
467 if (*ptr=='_')
468 return 1;
469 }
470 return 0;
471}
472
473
474/* replace various Unicode operator symbols with stanard ASCII equivalents */
475void
476replace_operators(char *input)
477{
478 struct{
479 char *unicode;
480 char replacement;
481 } repl_table[]={
482 {"\xE2\x80\x92", '-'}, /* U+2012: figure dash */
483 {"\xE2\x80\x93", '-'}, /* U+2013: en dash */
484 {"\xE2\x88\x92", '-'}, /* U+2212: minus */
485 {"\xC3\x97", '*'}, /* U+00D7: times */
486 {"\xE2\xA8\x89" ,'*'}, /* U+2A09: N-ary times operator */
487 {"\xC2\xB7", '*'}, /* U+00B7: middle dot */
488 {"\xE2\x8B\x85", '*'}, /* U+22C5: dot operator */
489 {"\xC3\xB7", '/'}, /* U+00F7: division sign */
490 {"\xE2\x88\x95", '/'}, /* U+2215: division slash */
491 {"\xE2\x81\x84", '|'}, /* U+2044: fraction slash */
492 {0,0}
493 };
494 char *inptr, *outptr, *ptr;
495
496 for (int i=0; repl_table[i].unicode; i++) {
497 inptr = outptr = input;
498 do {
499 ptr = strstr(inptr, repl_table[i].unicode); /* find next unicode symbol */
500 if (ptr) {
501 while (inptr < ptr) /* copy the input up to the unicode */
502 *outptr++ = *inptr++;
503 inptr = ptr + strlen(repl_table[i].unicode); /* skip over unicode */
504 *outptr++ = repl_table[i].replacement; /* Output replacement */
505 }
506 } while (ptr);
507
508 /* If replacement were made, copy remaining input to end of string */
509
510 if (inptr > input) {
511 while (*inptr)
512 *outptr++ = *inptr++;
513 *outptr = '\0';
514 }
515 }
516}
517
518
519
520/* Replace all control chars with a space */
521
522void
523replacectrlchars(char *string)
524{
525 for(;*string;string++)
526 if (iscntrl(*string))
527 *string = ' ';
528}
529
530/*
531 Fetch a line of data with backslash for continuation. The
532 parameter count is incremented to report the number of newlines
533 that are read so that line numbers can be accurately reported.
534*/
535
536char *
537fgetscont(char *buf, int size, FILE *file, int *count)
538{
539 if (!fgets(buf,size,file))
540 return 0;
541 (*count)++;
542 while(strlen(buf)>=2 && 0==strcmp(buf+strlen(buf)-2,"\\\n")){
543 (*count)++;
544 buf[strlen(buf)-2] = 0; /* delete trailing \n and \ char */
545 if (strlen(buf)>=size-1) /* return if the buffer is full */
546 return buf;
547 if (!fgets(buf+strlen(buf), size - strlen(buf), file))
548 return buf; /* already read some data so return success */
549 }
550 if (lastchar(buf) == '\\') { /* If last char of buffer is \ then */
551 ungetc('\\', file); /* we don't know if it is followed by */
552 lastchar(buf) = 0; /* a \n, so put it back and try again */
553 }
554 return buf;
555}
556
557
558/*
559 Gets arbitrarily long input data into a buffer using growbuffer().
560 Returns 0 if no data is read. Increments count by the number of
561 newlines read unless it points to NULL.
562
563 Replaces tabs and newlines with spaces before returning the result.
564*/
565
566char *
567fgetslong(char **buf, int *bufsize, FILE *file, int *count)
568{
569 int dummy;
570 if (!count)
571 count = &dummy;
572 if (!*bufsize) growbuffer(buf,bufsize);
573 if (!fgetscont(*buf, *bufsize, file, count))
574 return 0;
575 while (lastchar(*buf) != '\n' && !feof(file)){
576 growbuffer(buf, bufsize);
577 fgetscont(*buf+strlen(*buf), *bufsize-strlen(*buf), file, count);
578 (*count)--;
579 }
580 /* These nonprinting characters must be removed so that the test
581 for UTF-8 validity will work. */
582 replacectrlchars(*buf);
583 return *buf;
584}
585
586/* Allocates memory and aborts if malloc fails. */
587
588void *
589mymalloc(int bytes,const char *mesg)
590{
591 void *pointer;
592
593 pointer = malloc(bytes);
594 if (!pointer){
595 fprintf(stderr, "%s: memory allocation error %s\n", progname, mesg);
596 exit(EXIT_FAILURE);
597 }
598 return pointer;
599}
600
601
602/* Duplicates a string */
603
604char *
605dupstr(const char *str)
606{
607 char *ret;
608
609 ret = mymalloc(strlen(str) + 1,"(dupstr)");
610 strcpy(ret, str);
611 return ret;
612}
613
614/* Duplicates a string that is not null-terminated,
615 adding the null to the copy */
616
617char *
618dupnstr(const char *string, int length)
619{
620 char *newstr;
621 newstr = mymalloc(length+1,"(dupnstr)");
622 strncpy(newstr, string, length);
623 newstr[length]=0;
624 return newstr;
625}
626
627
628#ifdef SUPPORT_UTF8
629
630/*
631 The strwidth function gives the printed width of a UTF-8 byte sequence.
632 It will return -1 if the sequence is an invalid UTF-8 sequence or
633 if the sequence contains "nonprinting" characters. Note that \n and \t are
634 "nonprinting" characters.
635*/
636
637int
638strwidth(const char *str)
639{
640 wchar_t *widestr;
641 int len;
642
643 if (!utf8mode)
644 return strlen(str);
645 len = strlen(str)+1;
646 widestr = mymalloc(sizeof(wchar_t)*len, "(strwidth)");
647 len = mbsrtowcs(widestr, &str, len, NULL);
648
649 if (len==-1){
650 free(widestr);
651 return -1; /* invalid multibyte sequence */
652 }
653
654 len=wcswidth(widestr, len);
655 free(widestr);
656 return len;
657}
658#else
659# define strwidth strlen
660#endif
661
662
663
664/* hashing algorithm for units */
665
666unsigned
667uhash(const char *str)
668{
669 unsigned hashval;
670
671 for (hashval = 0; *str; str++)
672 hashval = *str + HASHNUMBER * hashval;
673 return (hashval % HASHSIZE);
674}
675
676
677/* Lookup a unit in the units table. Returns the definition, or NULL
678 if the unit isn't found in the table. */
679
680struct unitlist *
681ulookup(const char *str)
682{
683 struct unitlist *uptr;
684
685 for (uptr = utab[uhash(str)]; uptr; uptr = uptr->next)
686 if (strcmp(str, uptr->name) == 0)
687 return uptr;
688 return NULL;
689}
690
691/* Lookup a prefix in the prefix table. Finds the longest prefix that
692 matches the beginning of the input string. Returns NULL if no
693 prefixes match. */
694
695struct prefixlist *
696plookup(const char *str)
697{
698 struct prefixlist *prefix;
699 struct prefixlist *bestprefix=NULL;
700 int bestlength=0;
701
702 for (prefix = ptab[simplehash(str)]; prefix; prefix = prefix->next) {
703 if (prefix->len > bestlength && !strncmp(str, prefix->name, prefix->len)){
704 bestlength = prefix->len;
705 bestprefix = prefix;
706 }
707 }
708 return bestprefix;
709}
710
711/* Look up function in the function linked list */
712
713struct func *
714fnlookup(const char *str)
715{
716 struct func *funcptr;
717
718 for(funcptr=ftab[simplehash(str)];funcptr;funcptr = funcptr->next)
719 if (!strcmp(funcptr->name, str))
720 return funcptr;
721 return 0;
722}
723
724struct wantalias *
725aliaslookup(const char *str)
726{
727 struct wantalias *aliasptr;
728 for(aliasptr = firstalias; aliasptr; aliasptr=aliasptr->next)
729 if (!strcmp(aliasptr->name, str))
730 return aliasptr;
731 return 0;
732}
733
734
735/* Insert a new function into the linked list of functions */
736
737void
738addfunction(struct func *newfunc)
739{
740 int val;
741
742 val = simplehash(newfunc->name);
743 newfunc->next = ftab[val];
744 ftab[val] = newfunc;
745}
746
747
748/* Free the fields in the function except for the name so that it
749 can be redefined. It remains in position in the linked list. */
750void
751freefunction(struct func *funcentry)
752{
753 if (funcentry->table){
754 free(funcentry->table);
755 free(funcentry->tableunit);
756 } else {
757 free(funcentry->forward.param);
758 free(funcentry->forward.def);
759 if (funcentry->forward.domain_min) free(funcentry->forward.domain_min);
760 if (funcentry->forward.domain_max) free(funcentry->forward.domain_max);
761 if (funcentry->inverse.domain_min) free(funcentry->inverse.domain_min);
762 if (funcentry->inverse.domain_max) free(funcentry->inverse.domain_max);
763 if (funcentry->forward.dimen) free(funcentry->forward.dimen);
764 if (funcentry->inverse.dimen) free(funcentry->inverse.dimen);
765 if (funcentry->inverse.def) free(funcentry->inverse.def);
766 if (funcentry->inverse.param) free(funcentry->inverse.param);
767 }
768}
769
770/* Remove leading and trailing spaces from the input */
771
772void
773removespaces(char *in)
774{
775 char *ptr;
776 if (*in) {
777 for(ptr = in + strlen(in) - 1; *ptr==' '; ptr--); /* Last non-space */
778 *(ptr+1)=0;
779 if (*in==' '){
780 ptr = in + strspn(in," ");
781 memmove(in, ptr, strlen(ptr)+1);
782 }
783 }
784}
785
786
787/*
788 Looks up an inverse function given as a ~ character followed by
789 spaces and then the function name. The spaces will be deleted as a
790 side effect. If an inverse function is found returns the function
791 pointer, otherwise returns null.
792*/
793
794struct func *
795invfnlookup(char *str)
796{
797 if (*str != '~')
798 return 0;
799 removespaces(str+1);
800 return fnlookup(str+1);
801}
802
803
804char *
805strip_comment(char *line)
806{
807 char *comment = 0;
808
809 if ((line = strchr(line,COMMENTCHAR))) {
810 comment = line+1;
811 *line = 0;
812 }
813 return comment;
814}
815
816
817/* Print string but replace two consecutive spaces with one space. */
818
819void
820tightprint(FILE *outfile, char *string)
821{
822 while(*string){
823 fputc(*string, outfile);
824 if (*string != ' ') string++;
825 else while(*string==' ') string++;
826 }
827}
828
829/*
830 Copy string to buf, replacing two or more consecutive spaces with
831 one space.
832*/
833void
834tightbufprint(char *buf, char *string)
835{
836 while(*string) {
837 *buf++ = *string;
838 if (*string != ' ')
839 string++;
840 else {
841 while(*string==' ')
842 string++;
843 }
844 }
845 *buf = '\0';
846}
847
848
849#define readerror (goterr=1) && errfile && fprintf
850
851#define VAGUE_ERR "%s: error in units file '%s' line %d\n", \
852 progname, file, linenum
853
854/* Print out error message encountered while reading the units file. */
855
856
857/*
858 Splits the line into two parts. The first part is space delimited.
859 The second part is everything else. Removes trailing spaces from
860 the second part. Returned items are null if no parameter was found.
861*/
862
863void
864splitline(char *line, char **first, char **second)
865{
866 *second = 0;
867 *first = strtok(line, " ");
868 if (*first){
869 *second = strtok(0, "\n");
870 if (*second){
871 removespaces(*second);
872 if (emptystr(*second))
873 *second = 0;
874 }
875 }
876}
877
878
879/* see if character is part of a valid decimal number */
880
881int
882isdecimal(char c)
883{
884 return strchr(digits, c) != NULL;
885}
886
887
888/*
889 Check for some invalid unit names. Print error message. Returns 1 if
890 unit name is bad, zero otherwise.
891*/
892int
893checkunitname(char *name, int linenum, char *file, FILE *errfile)
894{
895 char nonunitchars[] = "~;+-*/|^)"; /* Also defined in parse.y with a few more characters */
896 char **ptr;
897 char *cptr;
898
899 if ((cptr=strpbrk(name, nonunitchars))){
900 if (errfile) fprintf(errfile,
901 "%s: unit '%s' in units file '%s' on line %d ignored. It contains invalid character '%c'\n",
902 progname, name, file, linenum, *cptr);
903 return 1;
904 }
905 if (strchr(digits, name[0])){
906 if (errfile) fprintf(errfile,
907 "%s: unit '%s' in units file '%s' on line %d ignored. It starts with a digit\n",
908 progname, name, file, linenum);
909 return 1;
910 }
911 for(ptr=builtins;*ptr;ptr++)
912 if (!strcmp(name, *ptr)){
913 if (errfile) fprintf(errfile,
914 "%s: redefinition of built-in function '%s' in file '%s' on line %d ignored.\n",
915 progname, name, file, linenum);
916 return 1;
917 }
918 for(ptr=all_commands;*ptr;ptr++)
919 if (!strcmp(name, *ptr)){
920 if (errfile) fprintf(errfile,
921 "%s: unit name '%s' in file '%s' on line %d may be hidden by command with the same name.\n",
922 progname, name, file, linenum);
923 }
924 return 0;
925}
926
927int
928newunit(char *unitname, char *unitdef, int *count, int linenum,
929 char *file,FILE *errfile, int redefine, int userunit)
930{
931 struct unitlist *uptr;
932 unsigned hashval;
933
934 /* units ending with '_' create ambiguity for exponents */
935
936 if ((unitname[0]=='_' && !userunit) || lastchar(unitname)=='_'){
937 if (errfile) fprintf(errfile,
938 "%s: unit '%s' on line %d of '%s' ignored. It starts or ends with '_'\n",
939 progname, unitname, linenum, file);
940 return E_BADFILE;
941 }
942
943 /* Units that end in [2-9] can never be accessed */
944 if (strchr(".,23456789", lastchar(unitname)) && !hassubscript(unitname)){
945 if (errfile) fprintf(errfile,
946 "%s: unit '%s' on line %d of '%s' ignored. %s\n",
947 progname, unitname, linenum, file,errormsg[E_UNITEND]);
948 return E_BADFILE;
949 }
950
951 if (checkunitname(unitname, linenum, file, errfile))
952 return E_BADFILE;
953
954 if ((uptr=ulookup(unitname))) { /* Is it a redefinition? */
955 if (flags.unitcheck && errfile && !redefine)
956 fprintf(errfile,
957 "%s: unit '%s' defined on line %d of '%s' is redefined on line %d of '%s'.\n",
958 progname, unitname, uptr->linenumber,uptr->file,
959 linenum, file);
960 free(uptr->value);
961 } else {
962 /* make new units table entry */
963
964 uptr = (struct unitlist *) mymalloc(sizeof(*uptr),"(newunit)");
965 uptr->name = dupstr(unitname);
966
967 /* install unit name/value pair in list */
968
969 hashval = uhash(uptr->name);
970 uptr->next = utab[hashval];
971 utab[hashval] = uptr;
972 (*count)++;
973 }
974 uptr->value = dupstr(unitdef);
975 uptr->linenumber = linenum;
976 uptr->file = file;
977 return 0;
978}
979
980
981
982int
983newprefix(char *unitname, char *unitdef, int *count, int linenum,
984 char *file,FILE *errfile, int redefine)
985{
986 struct prefixlist *pfxptr;
987 unsigned pval;
988
989 lastchar(unitname) = 0;
990 if (checkunitname(unitname,linenum,file,errfile))
991 return E_BADFILE;
992 if ((pfxptr = plookup(unitname)) /* already there: redefinition */
993 && !strcmp(pfxptr->name, unitname)){
994 if (flags.unitcheck && errfile && !redefine)
995 fprintf(errfile,
996 "%s: prefix '%s-' defined on line %d of '%s' is redefined on line %d of '%s'.\n",
997 progname, unitname, pfxptr->linenumber,pfxptr->file,
998 linenum, file);
999 free(pfxptr->value);
1000 } else {
1001 pfxptr = (struct prefixlist *) mymalloc(sizeof(*pfxptr),"(newprefix)");
1002 pfxptr->name = dupstr(unitname);
1003 pfxptr->len = strlen(unitname);
1004 pval = simplehash(unitname);
1005 pfxptr->next = ptab[pval];
1006 ptab[pval] = pfxptr;
1007 (*count)++;
1008 }
1009 pfxptr->value = dupstr(unitdef);
1010 pfxptr->linenumber = linenum;
1011 pfxptr->file = file;
1012 return 0;
1013}
1014
1015
1016/*
1017 parsepair() looks for data of the form [text1,text2] where the ',' is a
1018 specified delimiter. The second argument, text2, is optional and if it's
1019 missing then second is set to NULL. The parameters are allowed to be
1020 empty strings. The function returns the first character after the
1021 closing bracket if no errors occur or the NULL pointer on error.
1022*/
1023
1024char *
1025parsepair(char *input, char **first, char **second,
1026 int *firstopen, int *secondopen, char delimiter, int checkopen,
1027 char *unitname, int linenum, char *file,FILE *errfile)
1028{
1029 char *start, *end, *middle;
1030
1031 start = strpbrk(input, checkopen?"[(":"[");
1032 if (!start){
1033 if (errfile) fprintf(errfile,
1034 "%s: expecting '[' %s in definition of '%s' in '%s' line %d\n",
1035 progname, checkopen ? "or '('":"", unitname, file, linenum);
1036 return 0;
1037 }
1038 if (*start=='(') *firstopen=1;
1039 else *firstopen=0;
1040 *start++=0;
1041 removespaces(input);
1042 if (!emptystr(input)){
1043 if (errfile) fprintf(errfile,
1044 "%s: unexpected characters before '%c' in definition of '%s' in '%s' line %d\n",
1045 progname, *firstopen?'(':'[',unitname, file, linenum);
1046 return 0;
1047 }
1048 end = strpbrk(start, checkopen?"])":"]");
1049 if (!end){
1050 if (errfile) fprintf(errfile,
1051 "%s: expecting ']' %s in definition of '%s' in '%s' line %d\n",
1052 progname, checkopen?"or ')'":"",unitname, file, linenum);
1053 return 0;
1054 }
1055 if (*end==')') *secondopen=1;
1056 else *secondopen=0;
1057 *end++=0;
1058
1059 middle = strchr(start,delimiter);
1060
1061 if (middle){
1062 *middle++=0;
1063 removespaces(middle);
1064 *second = middle;
1065 } else
1066 *second = 0;
1067
1068 removespaces(start);
1069 *first = start;
1070 return end;
1071}
1072
1073
1074/*
1075 Extract numbers from two text strings and place them into pointers.
1076 Has two error codes for decreasing interval or bad numbers in the
1077 text strings. Returns 0 on success.
1078*/
1079
1080#define EI_ERR_DEC 1 /* Decreasing interval */
1081#define EI_ERR_MALF 2 /* Malformed number */
1082
1083int
1084extract_interval(char *first, char *second,
1085 double **firstout, double **secondout)
1086{
1087 double val;
1088 char *end;
1089
1090 if (!emptystr(first)){
1091 val = strtod(first, &end);
1092 if (*end)
1093 return EI_ERR_MALF;
1094 else {
1095 *firstout=(double *)mymalloc(sizeof(double), "(extract_interval)");
1096 **firstout = val;
1097 }
1098 }
1099 if (nonempty(second)) {
1100 val = strtod(second, &end);
1101 if (*end)
1102 return EI_ERR_MALF;
1103 else if (*firstout && **firstout>=val)
1104 return EI_ERR_DEC;
1105 else {
1106 *secondout=(double *)mymalloc(sizeof(double), "(extract_interval)");
1107 **secondout = val;
1108 }
1109 }
1110 return 0;
1111}
1112
1113
1114
1115void
1116copyfunctype(struct functype *dest, struct functype *src)
1117{
1118 dest->domain_min_open = src->domain_min_open;
1119 dest->domain_max_open = src->domain_max_open;
1120 dest->param = dest->def = dest->dimen = NULL;
1121 dest->domain_min = dest->domain_max = NULL;
1122 if (src->param) dest->param = dupstr(src->param);
1123 if (src->def) dest->def = dupstr(src->def);
1124 if (src->dimen) dest->dimen = dupstr(src->dimen);
1125 if (src->domain_min){
1126 dest->domain_min = (double *) mymalloc(sizeof(double), "(copyfunctype)");
1127 *dest->domain_min = *src->domain_min;
1128 }
1129 if (src->domain_max){
1130 dest->domain_max = (double *) mymalloc(sizeof(double), "(copyfunctype)");
1131 *dest->domain_max = *src->domain_max;
1132 }
1133}
1134
1135
1136int
1137copyfunction(char *unitname, char *funcname, int *count, int linenum,
1138 char *file, FILE *errfile)
1139{
1140 struct func *source, *funcentry;
1141 int i;
1142 if (checkunitname(unitname, linenum, file, errfile))
1143 return E_BADFILE;
1144 removespaces(funcname);
1145 i = strlen(funcname)-2; /* strip trailing () if present */
1146 if (i>0 && !strcmp(funcname+i,"()"))
1147 funcname[i]=0;
1148 source = fnlookup(funcname);
1149 if (!source) {
1150 if (errfile){
1151 if (!strpbrk(funcname," ;][()+*/-^"))
1152 fprintf(errfile,"%s: bad definition for '%s' in '%s' line %d, function '%s' not defined\n",
1153 progname, unitname, file, linenum, funcname);
1154 else
1155 fprintf(errfile,"%s: bad function definition of '%s' in '%s' line %d\n",
1156 progname,unitname,file,linenum);
1157 }
1158 return E_BADFILE;
1159 }
1160 if ((funcentry=fnlookup(unitname))){
1161 if (flags.unitcheck && errfile)
1162 fprintf(errfile,
1163 "%s: function '%s' defined on line %d of '%s' is redefined on line %d of '%s'.\n",
1164 progname, unitname, funcentry->linenumber,funcentry->file,
1165 linenum, file);
1166 freefunction(funcentry);
1167 } else {
1168 funcentry = (struct func*)mymalloc(sizeof(struct func),"(newfunction)");
1169 funcentry->name = dupstr(unitname);
1170 addfunction(funcentry);
1171 (*count)++;
1172 }
1173 funcentry->linenumber = linenum;
1174 funcentry->file = file;
1175 funcentry->skip_error_check = source->skip_error_check;
1176 if (source->table){
1177 funcentry->tablelen = source->tablelen;
1178 funcentry->tableunit = dupstr(source->tableunit);
1179 funcentry->table = (struct pair *)
1180 mymalloc(sizeof(struct pair)*funcentry->tablelen, "(copyfunction)");
1181 for(i=0;i<funcentry->tablelen;i++){
1182 funcentry->table[i].location = source->table[i].location;
1183 funcentry->table[i].value = source->table[i].value;
1184 }
1185 } else {
1186 funcentry->table = 0;
1187 copyfunctype(&funcentry->forward, &source->forward);
1188 copyfunctype(&funcentry->inverse, &source->inverse);
1189 }
1190 return 0;
1191}
1192
1193
1194#define FREE_STUFF {if (forward_dim) free(forward_dim);\
1195 if (inverse_dim) free(inverse_dim);\
1196 if (domain_min) free(domain_min);\
1197 if (domain_max) free(domain_max);\
1198 if (range_min) free(range_min);\
1199 if (range_max) free(range_max);}
1200
1201#define REPEAT_ERR \
1202 if (errfile) fprintf(errfile, \
1203 "%s: keyword '%s' repeated in definition of '%s' on line %d of '%s'.\n",\
1204 progname,fnkeywords[i].word,unitname, linenum, file)
1205
1206int
1207newfunction(char *unitname, char *unitdef, int *count,
1208 int linenum, char *file,FILE *errfile, int redefine)
1209{
1210 char *start, *end, *inv, *forward_dim, *inverse_dim, *first, *second;
1211 double *domain_min, *domain_max, *range_min, *range_max;
1212 struct func *funcentry;
1213 int looking_for_keywords,i, firstopen, secondopen;
1214 int domain_min_open, domain_max_open, range_min_open, range_max_open;
1215 int noerror = 0;
1216
1217 if (*unitname=='('){
1218 if (errfile) fprintf(errfile,
1219 "%s: unit '%s' on line %d of '%s' ignored. It starts with a '('\n",
1220 progname, unitname, linenum, file);
1221 return E_BADFILE;
1222 }
1223 /* coverity[returned_null] */
1224 start = strchr(unitname,'(');
1225 end = strchr(unitname,')');
1226 *start++ = 0;
1227
1228 if (checkunitname(unitname,linenum,file,errfile))
1229 return E_BADFILE;
1230
1231 if (start==end) /* no argument: function() so make a function copy */
1232 return copyfunction(unitname, unitdef, count, linenum, file, errfile);
1233
1234 if (!end || strlen(end)>1){
1235 if (errfile) fprintf(errfile,
1236 "%s: bad function definition of '%s' in '%s' line %d\n",
1237 progname,unitname,file,linenum);
1238 return E_BADFILE;
1239 }
1240 *end=0;
1241 forward_dim = NULL;
1242 inverse_dim = NULL;
1243 domain_min = NULL;
1244 domain_max = NULL;
1245 range_min = NULL;
1246 range_max = NULL;
1247 domain_min_open = 0;
1248 domain_max_open = 0;
1249 range_min_open = 0;
1250 range_max_open = 0;
1251 looking_for_keywords=1;
1252 while (looking_for_keywords) {
1253 looking_for_keywords = 0;
1254 for(i=0;fnkeywords[i].word;i++){
1255 if (startswith(unitdef, fnkeywords[i].word)){
1256 looking_for_keywords = 1; /* found keyword so keep looking */
1257 unitdef+=strlen(fnkeywords[i].word);
1258 if (fnkeywords[i].checkopen!=CO_NOARG){
1259 unitdef = parsepair(unitdef,&first, &second, &firstopen, &secondopen,
1260 fnkeywords[i].delimit, fnkeywords[i].checkopen,
1261 unitname, linenum, file,errfile);
1262 if (!unitdef){
1263 FREE_STUFF;
1264 return E_BADFILE;
1265 }
1266 removespaces(unitdef);
1267 }
1268 if (i==FN_NOERROR)
1269 noerror = 1;
1270 if (i==FN_UNITS){
1271 if (forward_dim || inverse_dim){
1272 REPEAT_ERR;
1273 return E_BADFILE;
1274 }
1275 forward_dim = dupstr(first);
1276 if (second)
1277 inverse_dim = dupstr(second);
1278 }
1279 if (i==FN_DOMAIN){
1280 int err=0;
1281 if (domain_min || domain_max){
1282 REPEAT_ERR;
1283 return E_BADFILE;
1284 }
1285 err = extract_interval(first,second,&domain_min, &domain_max);
1286 domain_min_open = firstopen;
1287 domain_max_open = secondopen;
1288 if (err)
1289 FREE_STUFF;
1290 if (err==EI_ERR_DEC){
1291 if (errfile) fprintf(errfile,
1292 "%s: second endpoint for domain must be greater than the first\n in definition of '%s' in '%s' line %d\n",
1293 progname, unitname, file, linenum);
1294 return E_BADFILE;
1295 }
1296 if (err==EI_ERR_MALF){
1297 if (errfile) fprintf(errfile,
1298 "%s: malformed domain in definition of '%s' in '%s' line %d\n",
1299 progname, unitname, file, linenum);
1300 return E_BADFILE;
1301 }
1302 }
1303 if (i==FN_RANGE){
1304 int err=0;
1305 if (range_min || range_max){
1306 REPEAT_ERR;
1307 FREE_STUFF;
1308 return E_BADFILE;
1309 }
1310 err = extract_interval(first,second,&range_min, &range_max);
1311 range_min_open = firstopen;
1312 range_max_open = secondopen;
1313 if (err)
1314 FREE_STUFF;
1315 if (err==EI_ERR_DEC){
1316 if (errfile) fprintf(errfile,
1317 "%s: second endpoint for range must be greater than the first\n in definition of '%s' in '%s' line %d\n",
1318 progname, unitname, file, linenum);
1319 return E_BADFILE;
1320 }
1321 if (err==EI_ERR_MALF){
1322 if (errfile) fprintf(errfile,
1323 "%s: malformed range in definition of '%s' in '%s' line %d\n",
1324 progname, unitname, file, linenum);
1325 return E_BADFILE;
1326 }
1327 }
1328 }
1329 }
1330 }
1331
1332 if (emptystr(unitdef)){
1333 if (errfile) fprintf(errfile,
1334 "%s: function '%s' lacks a definition at line %d of '%s'\n",
1335 progname, unitname, linenum, file);
1336 FREE_STUFF;
1337 return E_BADFILE;
1338 }
1339
1340 if (*unitdef=='['){
1341 if (errfile) fprintf(errfile,
1342 "%s: function '%s' missing keyword before '[' on line %d of '%s'\n",
1343 progname, unitname, linenum, file);
1344 FREE_STUFF;
1345 return E_BADFILE;
1346 }
1347
1348 /* Check that if domain and range are specified and nonzero then the
1349 units are given. Otherwise these are meaningless. */
1350 if (!forward_dim &&
1351 ((domain_min && *domain_min) || (domain_max && *domain_max))){
1352 if (errfile)
1353 fprintf(errfile,"%s: function '%s' defined on line %d of '%s' has domain with no units.\n",
1354 progname, unitname, linenum, file);
1355 FREE_STUFF;
1356 return E_BADFILE;
1357 }
1358 if (!inverse_dim &&
1359 ((range_min && *range_min) || (range_max && *range_max))){
1360 if (errfile)
1361 fprintf(errfile,"%s: function '%s' defined on line %d of '%s' has range with no units.\n",
1362 progname, unitname, linenum, file);
1363 FREE_STUFF;
1364 return E_BADFILE;
1365 }
1366 if ((funcentry=fnlookup(unitname))){
1367 if (flags.unitcheck && errfile && !redefine)
1368 fprintf(errfile,
1369 "%s: function '%s' defined on line %d of '%s' is redefined on line %d of '%s'.\n",
1370 progname, unitname, funcentry->linenumber,funcentry->file,
1371 linenum, file);
1372 freefunction(funcentry);
1373 } else {
1374 funcentry = (struct func*)mymalloc(sizeof(struct func),"(newfunction)");
1375 funcentry->name = dupstr(unitname);
1376 addfunction(funcentry);
1377 (*count)++;
1378 }
1379 funcentry->table = 0;
1380 funcentry->skip_error_check = noerror;
1381 funcentry->forward.dimen = forward_dim;
1382 funcentry->inverse.dimen = inverse_dim;
1383 funcentry->forward.domain_min = domain_min;
1384 funcentry->forward.domain_max = domain_max;
1385 funcentry->inverse.domain_min = range_min;
1386 funcentry->inverse.domain_max = range_max;
1387 funcentry->forward.domain_min_open = domain_min_open;
1388 funcentry->forward.domain_max_open = domain_max_open;
1389 funcentry->inverse.domain_min_open = range_min_open;
1390 funcentry->inverse.domain_max_open = range_max_open;
1391 inv = strchr(unitdef,FUNCSEPCHAR);
1392 if (inv)
1393 *inv++ = 0;
1394 funcentry->forward.param = dupstr(start);
1395 removespaces(unitdef);
1396 funcentry->forward.def = dupstr(unitdef);
1397 if (inv){
1398 removespaces(inv);
1399 funcentry->inverse.def = dupstr(inv);
1400 funcentry->inverse.param = dupstr(unitname);
1401 }
1402 else {
1403 funcentry->inverse.def = 0;
1404 funcentry->inverse.param = 0;
1405 }
1406 funcentry->linenumber = linenum;
1407 funcentry->file = file;
1408 return 0;
1409}
1410
1411
1412int
1413newtable(char *unitname,char *unitdef, int *count,
1414 int linenum, char *file,FILE *errfile, int redefine)
1415{
1416 char *start, *end;
1417 char *tableunit;
1418 int tablealloc, tabpt;
1419 struct pair *tab;
1420 struct func *funcentry;
1421 int noerror = 0;
1422
1423 /* coverity[returned_null] */
1424 tableunit = strchr(unitname,'[');
1425 end = strchr(unitname,']');
1426 *tableunit++=0;
1427 if (checkunitname(unitname, linenum, file, errfile))
1428 return E_BADFILE;
1429 if (!end){
1430 if (errfile) fprintf(errfile,"%s: missing ']' in units file '%s' line %d\n",
1431 progname,file,linenum);
1432 return E_BADFILE;
1433 }
1434 if (strlen(end)>1){
1435 if (errfile) fprintf(errfile,
1436 "%s: unexpected characters after ']' in units file '%s' line %d\n",
1437 progname,file,linenum);
1438 return E_BADFILE;
1439 }
1440 *end=0;
1441 tab = (struct pair *)mymalloc(sizeof(struct pair)*20, "(newtable)");
1442 tablealloc=20;
1443 tabpt = 0;
1444 start = unitdef;
1445 if (startswith(start, NOERROR_KEYWORD)) {
1446 noerror = 1;
1447 start += strlen(NOERROR_KEYWORD);
1448 removespaces(start);
1449 }
1450 while (1) {
1451 if (tabpt>=tablealloc){
1452 tablealloc+=20;
1453 tab = (struct pair *)realloc(tab,sizeof(struct pair)*tablealloc);
1454 if (!tab){
1455 if (errfile) fprintf(errfile, "%s: memory allocation error (newtable)\n",
1456 progname);
1457 return E_MEMORY;
1458 }
1459 }
1460 tab[tabpt].location = strtod(start,&end);
1461 if (start==end || (!emptystr(end) && *end !=' ')){
1462 if (!emptystr(start)) {
1463 if (strlen(start)>15) start[15]=0; /* Truncate for error msg display */
1464 if (errfile) fprintf(errfile,
1465 "%s: cannot parse table definition %s at '%s' on line %d of '%s'\n",
1466 progname, unitname, start, linenum, file);
1467 free(tab);
1468 return E_BADFILE;
1469 }
1470 break;
1471 }
1472 if (tabpt>0 && tab[tabpt].location<=tab[tabpt-1].location){
1473 if (errfile)
1474 fprintf(errfile,"%s: points don't increase (" ERRNUMFMT " to " ERRNUMFMT
1475 ") in units file '%s' line %d\n",
1476 progname, tab[tabpt-1].location, tab[tabpt].location,
1477 file, linenum);
1478 free(tab);
1479 return E_BADFILE;
1480 }
1481 start=end+strspn(end," ");
1482 tab[tabpt].value = strtod(start,&end);
1483 if (start==end){
1484 if (errfile)
1485 fprintf(errfile,"%s: missing value after " ERRNUMFMT " in units file '%s' line %d\n",
1486 progname, tab[tabpt].location, file, linenum);
1487 free(tab);
1488 return E_BADFILE;
1489 }
1490 tabpt++;
1491 start=end+strspn(end," ,");
1492 }
1493 if ((funcentry=fnlookup(unitname))){
1494 if (flags.unitcheck && errfile && !redefine)
1495 fprintf(errfile,
1496 "%s: unit '%s' defined on line %d of '%s' is redefined on line %d of '%s'.\n",
1497 progname, unitname, funcentry->linenumber,funcentry->file,
1498 linenum, file);
1499 freefunction(funcentry);
1500 } else {
1501 funcentry = (struct func *)mymalloc(sizeof(struct func),"(newtable)");
1502 funcentry->name = dupstr(unitname);
1503 addfunction(funcentry);
1504 (*count)++;
1505 }
1506 funcentry->tableunit = dupstr(tableunit);
1507 funcentry->tablelen = tabpt;
1508 funcentry->table = tab;
1509 funcentry->skip_error_check = noerror;
1510 funcentry->linenumber = linenum;
1511 funcentry->file = file;
1512 return 0;
1513}
1514
1515
1516int
1517newalias(char *unitname, char *unitdef,int linenum, char *file,FILE *errfile)
1518{
1519 struct wantalias *aliasentry;
1520
1521 if (!strchr(unitdef, UNITSEPCHAR)){
1522 if (errfile) fprintf(errfile,
1523 "%s: unit list missing '%c' on line %d of '%s'\n",
1524 progname, UNITSEPCHAR, linenum, file);
1525 return E_BADFILE;
1526 }
1527 if ((aliasentry=aliaslookup(unitname))){ /* duplicate alias */
1528 if (flags.unitcheck && errfile)
1529 fprintf(errfile,
1530 "%s: unit list '%s' defined on line %d of '%s' is redefined on line %d of '%s'.\n",
1531 progname, unitname, aliasentry->linenumber,
1532 aliasentry->file, linenum, file);
1533 free(aliasentry->definition);
1534 } else {
1535 aliasentry = (struct wantalias *)
1536 mymalloc(sizeof(struct wantalias),"(newalias)");
1537 aliasentry->name = dupstr(unitname);
1538 aliasentry->next = 0;
1539 *aliaslistend = aliasentry;
1540 aliaslistend = &aliasentry->next;
1541 }
1542 aliasentry->definition = dupstr(unitdef);
1543 aliasentry->linenumber = linenum;
1544 aliasentry->file = file;
1545 return 0;
1546}
1547
1548
1549/*
1550 Check environment variable name to see if its value appears on the
1551 space delimited text string pointed to by list. Returns 2 if the
1552 environment variable is not set, return 1 if its value appears on
1553 the list and zero otherwise.
1554*/
1555
1556int
1557checkvar(char *name, char *list)
1558{
1559 char *listitem;
1560 name = getenv(name);
1561 if (!name)
1562 return 2;
1563 listitem = strtok(list," ");
1564 while (listitem){
1565 if (!strcmp(name, listitem))
1566 return 1;
1567 listitem = strtok(0," ");
1568 }
1569 return 0;
1570}
1571
1572
1573#ifdef NO_SETENV
1574int
1575setenv(const char *name, const char *val, int overwrite)
1576{
1577 char *environment;
1578
1579 if (!overwrite && getenv(name) != NULL)
1580 return 0;
1581 environment = (char *) malloc(strlen(name) + strlen(val) + 2);
1582 if (!environment)
1583 return 1;
1584 strcpy(environment, name);
1585 strcat(environment, "=");
1586 strcat(environment, val);
1587
1588 /* putenv() doesn't copy its argument, so don't free environment */
1589
1590#if defined (_WIN32) && defined (_MSC_VER)
1591 return _putenv(environment);
1592#else
1593 return putenv(environment);
1594#endif
1595}
1596#endif
1597
1598#ifdef _WIN32
1599# define isdirsep(c) ((c) == '/' || (c) == '\\')
1600# define hasdirsep(s) strpbrk((s),"/\\")
1601#else
1602# define isdirsep(c) ((c) == '/')
1603# define hasdirsep(s) strchr((s),'/')
1604#endif
1605#define isexe(s) ((strlen(s) == 4) && (tolower(s[1]) == 'e') \
1606 && (tolower(s[2]) == 'x') && (tolower(s[3]) == 'e'))
1607
1608/* Returns a pointer to the end of the pathname part of the
1609 specified filename */
1610
1611char *
1612pathend(char *filename)
1613{
1614 char *pointer;
1615
1616 for(pointer=filename+strlen(filename);pointer>filename;pointer--){
1617 if (isdirsep(*pointer)) {
1618 pointer++;
1619 break;
1620 }
1621 }
1622 return pointer;
1623}
1624
1625int
1626isfullpath(char *path)
1627{
1628#ifdef _WIN32
1629 /* handle Windows drive specifier */
1630 if (isalpha(*path) && *(path + 1) == ':')
1631 path += 2;
1632#endif
1633 return isdirsep(*path);
1634}
1635
1636/*
1637 Read in units data.
1638
1639 file - Filename to load
1640 errfile - File to receive messages about errors in the units database.
1641 Set it to 0 to suppress errors.
1642 unitcount, prefixcount, funccount - Return statistics to the caller.
1643 Must initialize to zero before calling.
1644 depth - Used to prevent recursive includes. Call with it set to zero.
1645
1646 The global variable progname is used in error messages.
1647*/
1648
1649int
1650readunits(char *file, FILE *errfile,
1651 int *unitcount, int *prefixcount, int *funccount, int depth)
1652{
1653 FILE *unitfile;
1654 char *line = 0, *lineptr, *unitdef, *unitname, *permfile;
1655 int linenum, linebufsize, goterr, retcode;
1656 int locunitcount, locprefixcount, locfunccount, redefinition;
1657 int wronglocale = 0; /* If set then we are currently reading data */
1658 int inlocale = 0; /* for the wrong locale so we should skip it */
1659 int in_utf8 = 0; /* If set we are reading utf8 data */
1660 int invar = 0; /* If set we are in data for an env variable.*/
1661 int wrongvar = 0; /* If set then we are not processing */
1662
1663 locunitcount = 0;
1664 locprefixcount = 0;
1665 locfunccount = 0;
1666 linenum = 0;
1667 linebufsize = 0;
1668 goterr = 0;
1669
1670 unitfile = openfile(file, "rt");
1671
1672 if (!unitfile){
1673 if (errfile)
1674 fprintf(errfile, "%s: Unable to read units file '%s': %s\n", progname, file, strerror(errno));
1675 return E_FILE;
1676 }
1677 growbuffer(&line,&linebufsize);
1678 /* coverity[alloc_fn] */
1679 permfile = dupstr(file); /* This is a permanent copy to reference in */
1680 /* the database. It is never freed. */
1681 while (!feof(unitfile)) {
1682 if (!fgetslong(&line, &linebufsize, unitfile, &linenum))
1683 break;
1684 if (linenum==1 && startswith(line, UTF8MARKER)){
1685 int i;
1686 for(lineptr=line,i=0;i<strlen(UTF8MARKER);i++, lineptr++)
1687 *lineptr=' ';
1688 }
1689 strip_comment(line);
1690 if (-1 == strwidth(line)){
1691 readerror(errfile, "%s: %s on line %d of '%s'\n",
1692 progname, invalid_utf8, linenum, file);
1693 continue;
1694 }
1695 replace_operators(line);
1696
1697 if (*line == COMMANDCHAR) { /* Process units file commands */
1698 unitname = strtok(line+1, " ");
1699 if (!unitname){
1700 readerror(errfile, VAGUE_ERR);
1701 continue;
1702 }
1703
1704 /* Check for locale and utf8 declarations. Any other commands
1705 most likely belong after these tests. */
1706 if (!strcmp(unitname,"var") || !strcmp(unitname,"varnot")){
1707 int not = 0;
1708 if (unitname[3]=='n')
1709 not = 1;
1710 unitname=strtok(0," ");
1711 unitdef=strtok(0,""); /* Get rest of the line */
1712 if (!unitname)
1713 readerror(errfile,
1714 "%s: no variable name specified on line %d of '%s'\n",
1715 progname, linenum, file);
1716 else if (!unitdef)
1717 readerror(errfile,
1718 "%s: no value specified on line %d of '%s'\n",
1719 progname, linenum, file);
1720 else if (invar)
1721 readerror(errfile,
1722 "%s: nested var statements not allowed, line %d of '%s'\n",
1723 progname, linenum, file);
1724 else {
1725 int check;
1726 invar = 1;
1727 check = checkvar(unitname, unitdef);
1728 if (check==2){
1729 readerror(errfile,
1730 "%s: environment variable %s not set at line %d of '%s'\n",
1731 progname, unitname, linenum, file);
1732 wrongvar = 1;
1733 }
1734 else if (!(not^check))
1735 wrongvar = 1;
1736 }
1737 continue;
1738 }
1739 else if (!strcmp(unitname, "endvar")){
1740 if (!invar)
1741 readerror(errfile,
1742 "%s: unmatched !endvar on line %d of '%s'\n",
1743 progname, linenum, file);
1744 wrongvar = 0;
1745 invar = 0;
1746 continue;
1747 }
1748 else if (!strcmp(unitname,"locale")){
1749 unitname = strtok(0, " ");
1750 if (!unitname)
1751 readerror(errfile,
1752 "%s: no locale specified on line %d of '%s'\n",
1753 progname, linenum, file);
1754 else if (inlocale)
1755 readerror(errfile,
1756 "%s: nested locales not allowed, line %d of '%s'\n",
1757 progname, linenum, file);
1758 else {
1759 inlocale = 1;
1760 if (strcmp(unitname,mylocale)) /* locales don't match */
1761 wronglocale = 1;
1762 }
1763 continue;
1764 }
1765 else if (!strcmp(unitname, "endlocale")){
1766 if (!inlocale)
1767 readerror(errfile,
1768 "%s: unmatched !endlocale on line %d of '%s'\n",
1769 progname, linenum, file);
1770 wronglocale = 0;
1771 inlocale = 0;
1772 continue;
1773 }
1774 else if (!strcmp(unitname, "utf8")){
1775 if (in_utf8)
1776 readerror(errfile,"%s: nested utf8 not allowed, line %d of '%s'\n",
1777 progname, linenum, file);
1778 else in_utf8 = 1;
1779 continue;
1780 }
1781 else if (!strcmp(unitname, "endutf8")){
1782 if (!in_utf8)
1783 readerror(errfile,"%s: unmatched !endutf8 on line %d of '%s'\n",
1784 progname, linenum, file);
1785 in_utf8 = 0;
1786 continue;
1787 }
1788 if (in_utf8 && !utf8mode) continue;
1789 if (wronglocale || wrongvar) continue;
1790
1791 if (!strcmp(unitname,"prompt")){
1792 unitname = strtok(0,""); /* Rest of the line */
1793 if (promptprefix)
1794 free(promptprefix);
1795 if (!unitname)
1796 promptprefix=0;
1797 else
1798 promptprefix = dupstr(unitname);
1799 continue;
1800 }
1801 if (!strcmp(unitname,"message")){
1802 unitname = strtok(0,""); /* Rest of the line */
1803 if (!flags.quiet){
1804 if (unitname) logputs(unitname);
1805 logputchar('\n');
1806 }
1807 continue;
1808 }
1809 else if (!strcmp(unitname,"set")) {
1810 unitname = strtok(0," ");
1811 unitdef = strtok(0," ");
1812 if (!unitname)
1813 readerror(errfile,
1814 "%s: no variable name specified on line %d of '%s'\n",
1815 progname, linenum, file);
1816 else if (!unitdef)
1817 readerror(errfile,
1818 "%s: no value specified on line %d of '%s'\n",
1819 progname, linenum, file);
1820 else
1821 setenv(unitname, unitdef, 0);
1822 continue;
1823 }
1824 else if (!strcmp(unitname,"unitlist")){
1825 splitline(0,&unitname, &unitdef); /* 0 continues strtok call */
1826 if (!unitname || !unitdef)
1827 readerror(errfile,VAGUE_ERR);
1828 else {
1829 if (newalias(unitname, unitdef, linenum, permfile, errfile))
1830 goterr = 1;
1831 }
1832 continue;
1833 }
1834 else if (!strcmp(unitname, "include")){
1835 if (depth>MAXINCLUDE){
1836 readerror(errfile,
1837 "%s: max include depth of %d exceeded in file '%s' line %d\n",
1838 progname, MAXINCLUDE, file, linenum);
1839 } else {
1840 int readerr;
1841 char *includefile;
1842
1843 unitname = strtok(0, " ");
1844 if (!unitname){
1845 readerror(errfile,
1846 "%s: missing include filename on line %d of '%s'\n",
1847 progname, linenum, file);
1848 continue;
1849 }
1850 includefile = mymalloc(strlen(file)+strlen(unitname)+1, "(readunits)");
1851 if (isfullpath(unitname))
1852 strcpy(includefile,unitname);
1853 else {
1854 strcpy(includefile,file);
1855 strcpy(pathend(includefile), unitname);
1856 }
1857
1858 readerr = readunits(includefile, errfile, unitcount, prefixcount,
1859 funccount, depth+1);
1860 if (readerr == E_MEMORY){
1861 fclose(unitfile);
1862 free(line);
1863 free(includefile);
1864 return readerr;
1865 }
1866 if (readerr == E_FILE) {
1867 readerror(errfile, "%s: file was included at line %d of file '%s'\n", progname,linenum, file);
1868 }
1869
1870 if (readerr)
1871 goterr = 1;
1872 free(includefile);
1873 }
1874 } else /* not a valid command */
1875 readerror(errfile,VAGUE_ERR);
1876 continue;
1877 }
1878 if (in_utf8 && !utf8mode) continue;
1879 if (wronglocale || wrongvar) continue;
1880 splitline(line, &unitname, &unitdef);
1881 if (!unitname) continue;
1882
1883 if (*unitname == REDEFCHAR){
1884 unitname++;
1885 redefinition=1;
1886 if (strlen(unitname)==0){
1887 readerror(errfile,
1888 "%s: expecting name of unit to redefine after '+' at line %d of '%s'\n",
1889 progname, linenum,file);
1890 continue;
1891 }
1892 } else
1893 redefinition=0;
1894 if (!strcmp(unitname,"-")) {
1895 readerror(errfile,
1896 "%s: expecting prefix name before '-' at line %d of '%s'\n",
1897 progname, linenum,file);
1898 continue;
1899 }
1900 if (!unitdef){
1901 readerror(errfile,
1902 "%s: unit '%s' lacks a definition at line %d of '%s'\n",
1903 progname, unitname, linenum, file);
1904 continue;
1905 }
1906
1907 if (lastchar(unitname) == '-'){ /* it's a prefix definition */
1908 if (newprefix(unitname,unitdef,&locprefixcount,linenum,
1909 permfile,errfile,redefinition))
1910 goterr=1;
1911 }
1912 else if (strchr(unitname,'[')){ /* table definition */
1913 retcode=newtable(unitname,unitdef,&locfunccount,linenum,
1914 permfile,errfile,redefinition);
1915 if (retcode){
1916 if (retcode != E_BADFILE){
1917 fclose(unitfile);
1918 free(line);
1919 return retcode;
1920 }
1921 goterr=1;
1922 }
1923 }
1924 else if (strchr(unitname,'(')){ /* function definition */
1925 if (newfunction(unitname,unitdef,&locfunccount,linenum,
1926 permfile,errfile,redefinition))
1927 goterr = 1;
1928 }
1929 else { /* ordinary unit definition */
1930 if (newunit(unitname,unitdef,&locunitcount,linenum,permfile,errfile,redefinition,0))
1931 goterr = 1;
1932 }
1933 }
1934 fclose(unitfile);
1935 free(line);
1936 if (unitcount)
1937 *unitcount+=locunitcount;
1938 if (prefixcount)
1939 *prefixcount+=locprefixcount;
1940 if (funccount)
1941 *funccount+=locfunccount;
1942 if (goterr)
1943 return E_BADFILE;
1944 else return 0;
1945}
1946
1947/* Initialize a unit to be equal to 1. */
1948
1949void
1950initializeunit(struct unittype *theunit)
1951{
1952 theunit->factor = 1.0;
1953 theunit->numerator[0] = theunit->denominator[0] = NULL;
1954}
1955
1956
1957/* Free a unit: frees all the strings used in the unit structure.
1958 Does not free the unit structure itself. */
1959
1960void
1961freeunit(struct unittype *theunit)
1962{
1963 char **ptr;
1964
1965 for(ptr = theunit->numerator; *ptr; ptr++)
1966 if (*ptr != NULLUNIT) free(*ptr);
1967 for(ptr = theunit->denominator; *ptr; ptr++)
1968 if (*ptr != NULLUNIT) free(*ptr);
1969
1970 /* protect against repeated calls to freeunit() */
1971
1972 theunit->numerator[0] = 0;
1973 theunit->denominator[0] = 0;
1974}
1975
1976
1977/* Print out a unit */
1978
1979void
1980showunit(struct unittype *theunit)
1981{
1982 char **ptr;
1983 int printedslash;
1984 int counter = 1;
1985
1986 logprintf(num_format.format, theunit->factor);
1987
1988 for (ptr = theunit->numerator; *ptr; ptr++) {
1989 if (ptr > theunit->numerator && **ptr &&
1990 !strcmp(*ptr, *(ptr - 1)))
1991 counter++;
1992 else {
1993 if (counter > 1)
1994 logprintf("%s%d", powerstring, counter);
1995 if (**ptr)
1996 logprintf(" %s", *ptr);
1997 counter = 1;
1998 }
1999 }
2000 if (counter > 1)
2001 logprintf("%s%d", powerstring, counter);
2002 counter = 1;
2003 printedslash = 0;
2004 for (ptr = theunit->denominator; *ptr; ptr++) {
2005 if (ptr > theunit->denominator && **ptr &&
2006 !strcmp(*ptr, *(ptr - 1)))
2007 counter++;
2008 else {
2009 if (counter > 1)
2010 logprintf("%s%d", powerstring, counter);
2011 if (**ptr) {
2012 if (!printedslash)
2013 logprintf(" /");
2014 printedslash = 1;
2015 logprintf(" %s", *ptr);
2016 }
2017 counter = 1;
2018 }
2019 }
2020 if (counter > 1)
2021 logprintf("%s%d", powerstring, counter);
2022}
2023
2024
2025/* qsort comparison function */
2026
2027int
2028compare(const void *item1, const void *item2)
2029{
2030 return strcmp(*(char **) item1, *(char **) item2);
2031}
2032
2033/* Sort numerator and denominator of a unit so we can compare different
2034 units */
2035
2036void
2037sortunit(struct unittype *theunit)
2038{
2039 char **ptr;
2040 int count;
2041
2042 for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++);
2043 qsort(theunit->numerator, count, sizeof(char *), compare);
2044 for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++);
2045 qsort(theunit->denominator, count, sizeof(char *), compare);
2046}
2047
2048
2049/* Cancels duplicate units that appear in the numerator and
2050 denominator. The input unit must be sorted. */
2051
2052void
2053cancelunit(struct unittype *theunit)
2054{
2055 char **den, **num;
2056 int comp;
2057
2058 den = theunit->denominator;
2059 num = theunit->numerator;
2060
2061 while (*num && *den) {
2062 comp = strcmp(*den, *num);
2063 if (!comp) { /* units match, so cancel them */
2064 if (*den!=NULLUNIT) free(*den);
2065 if (*num!=NULLUNIT) free(*num);
2066 *den++ = NULLUNIT;
2067 *num++ = NULLUNIT;
2068 } else if (comp < 0) /* Move up whichever pointer is alphabetically */
2069 den++; /* behind to look for future matches */
2070 else
2071 num++;
2072 }
2073}
2074
2075
2076/*
2077 Looks up the definition for the specified unit including prefix processing
2078 and plural removal.
2079
2080 Returns a pointer to the definition or a null pointer
2081 if the specified unit does not appear in the units table.
2082
2083 Sometimes the returned pointer will be a pointer to the special
2084 buffer created to hold the data. This buffer grows as needed during
2085 program execution.
2086
2087 Note that if you pass the output of lookupunit() back into the function
2088 again you will get correct results, but the data you passed in may get
2089 clobbered if it happened to be the internal buffer.
2090*/
2091
2092static int bufsize=0;
2093static char *buffer; /* buffer for lookupunit answers with prefixes */
2094
2095
2096/*
2097 Plural rules for english: add -s
2098 after x, sh, ch, ss add -es
2099 -y becomes -ies except after a vowel when you just add -s as usual
2100*/
2101
2102
2103char *
2104lookupunit(char *unit,int prefixok)
2105{
2106 char *copy;
2107 struct prefixlist *pfxptr;
2108 struct unitlist *uptr;
2109
2110 if ((uptr = ulookup(unit)))
2111 return uptr->value;
2112
2113 if (strwidth(unit)>2 && lastchar(unit) == 's') {
2114 copy = dupstr(unit);
2115 lastchar(copy) = 0;
2116 if (lookupunit(copy,prefixok)){
2117 while(strlen(copy)+1 > bufsize) {
2118 growbuffer(&buffer, &bufsize);
2119 }
2120 strcpy(buffer, copy); /* Note: returning looked up result seems */
2121 free(copy); /* better but it causes problems when it */
2122 return buffer; /* contains PRIMITIVECHAR. */
2123 }
2124 if (strlen(copy)>2 && lastchar(copy) == 'e') {
2125 lastchar(copy) = 0;
2126 if (lookupunit(copy,prefixok)){
2127 while (strlen(copy)+1 > bufsize) {
2128 growbuffer(&buffer,&bufsize);
2129 }
2130 strcpy(buffer,copy);
2131 free(copy);
2132 return buffer;
2133 }
2134 }
2135 if (strlen(copy)>2 && lastchar(copy) == 'i') {
2136 lastchar(copy) = 'y';
2137 if (lookupunit(copy,prefixok)){
2138 while (strlen(copy)+1 > bufsize) {
2139 growbuffer(&buffer,&bufsize);
2140 }
2141 strcpy(buffer,copy);
2142 free(copy);
2143 return buffer;
2144 }
2145 }
2146 free(copy);
2147 }
2148 if (prefixok && (pfxptr = plookup(unit))) {
2149 copy = unit + pfxptr->len;
2150 if (emptystr(copy) || lookupunit(copy,0)) {
2151 char *tempbuf;
2152 while (strlen(pfxptr->value)+strlen(copy)+2 > bufsize){
2153 growbuffer(&buffer, &bufsize);
2154 }
2155 tempbuf = dupstr(copy); /* copy might point into buffer */
2156 strcpy(buffer, pfxptr->value);
2157 strcat(buffer, " ");
2158 strcat(buffer, tempbuf);
2159 free(tempbuf);
2160 return buffer;
2161 }
2162 }
2163 return 0;
2164}
2165
2166
2167/* Points entries of product[] to the strings stored in tomove[].
2168 Leaves tomove pointing to a list of NULLUNITS. */
2169
2170int
2171moveproduct(char *product[], char *tomove[])
2172{
2173 char **dest, **src;
2174
2175 dest=product;
2176 for(src = tomove; *src; src++){
2177 if (*src == NULLUNIT) continue;
2178 for(; *dest && *dest != NULLUNIT; dest++);
2179 if (dest - product >= MAXSUBUNITS - 1) {
2180 return E_PRODOVERFLOW;
2181 }
2182 if (!*dest)
2183 *(dest + 1) = 0;
2184 *dest = *src;
2185 *src=NULLUNIT;
2186 }
2187 return 0;
2188}
2189
2190/*
2191 Make a copy of a product list. Note that no error checking is done
2192 for overflowing the product list because it is assumed that the
2193 source list doesn't overflow, so the destination list shouldn't
2194 overflow either. (This assumption could be false if the
2195 destination is not actually at the start of a product buffer.)
2196*/
2197
2198void
2199copyproduct(char **dest, char **source)
2200{
2201 for(;*source;source++,dest++) {
2202 if (*source==NULLUNIT)
2203 *dest = NULLUNIT;
2204 else
2205 *dest=dupstr(*source);
2206 }
2207 *dest=0;
2208}
2209
2210/* Make a copy of a unit */
2211
2212void
2213unitcopy(struct unittype *dest, struct unittype *source)
2214{
2215 dest->factor = source->factor;
2216 copyproduct(dest->numerator, source->numerator);
2217 copyproduct(dest->denominator, source->denominator);
2218}
2219
2220
2221/* Multiply left by right. In the process, all of the units are
2222 deleted from right (but it is not freed) */
2223
2224int
2225multunit(struct unittype *left, struct unittype *right)
2226{
2227 int myerr;
2228 left->factor *= right->factor;
2229 myerr = moveproduct(left->numerator, right->numerator);
2230 if (!myerr)
2231 myerr = moveproduct(left->denominator, right->denominator);
2232 return myerr;
2233}
2234
2235int
2236divunit(struct unittype *left, struct unittype *right)
2237{
2238 int myerr;
2239 left->factor /= right->factor;
2240 myerr = moveproduct(left->numerator, right->denominator);
2241 if (!myerr)
2242 myerr = moveproduct(left->denominator, right->numerator);
2243 return myerr;
2244}
2245
2246
2247/*
2248 reduces a product of symbolic units to primitive units.
2249 The three low bits are used to return flags:
2250
2251 bit 0 set if reductions were performed without error.
2252 bit 1 set if no reductions are performed.
2253 bit 2 set if an unknown unit is discovered.
2254
2255 Return values from multiple calls will be ORed together later.
2256 */
2257
2258#define DIDREDUCTION (1<<0)
2259#define NOREDUCTION (1<<1)
2260#define REDUCTIONERROR (1<<2)
2261#define CIRCULARDEF (1<<3)
2262
2263int
2264reduceproduct(struct unittype *theunit, int flip)
2265{
2266
2267 char *toadd;
2268 char **product;
2269 int didsomething = NOREDUCTION;
2270 struct unittype newunit;
2271 int ret;
2272 int itcount=0; /* Count iterations to catch infinite loops */
2273
2274 if (flip)
2275 product = theunit->denominator;
2276 else
2277 product = theunit->numerator;
2278
2279 for (; *product; product++) {
2280 for (;;) {
2281 if (!strlen(*product))
2282 break;
2283
2284 /* check for infinite loop */
2285 itcount++;
2286 if (itcount>MAXPRODUCTREDUCTIONS)
2287 return CIRCULARDEF;
2288
2289 toadd = lookupunit(*product,1);
2290 if (!toadd) {
2291 if (!irreducible)
2292 irreducible = dupstr(*product);
2293 return REDUCTIONERROR;
2294 }
2295 if (strchr(toadd, PRIMITIVECHAR))
2296 break;
2297 didsomething = DIDREDUCTION;
2298 if (*product != NULLUNIT) {
2299 free(*product);
2300 *product = NULLUNIT;
2301 }
2302 if (parseunit(&newunit, toadd, 0, 0))
2303 return REDUCTIONERROR;
2304 if (flip) ret=divunit(theunit,&newunit);
2305 else ret=multunit(theunit,&newunit);
2306 freeunit(&newunit);
2307 if (ret)
2308 return REDUCTIONERROR;
2309 }
2310 }
2311 return didsomething;
2312}
2313
2314
2315#if 0
2316void showunitdetail(struct unittype *foo)
2317{
2318 char **ptr;
2319
2320 printf("%.17g ", foo->factor);
2321
2322 for(ptr=foo->numerator;*ptr;ptr++)
2323 if (*ptr==NULLUNIT) printf("NULL ");
2324 else printf("`%s' ", *ptr);
2325 printf(" / ");
2326 for(ptr=foo->denominator;*ptr;ptr++)
2327 if (*ptr==NULLUNIT) printf("NULL ");
2328 else printf("`%s' ", *ptr);
2329 putchar('\n');
2330}
2331#endif
2332
2333
2334/*
2335 Reduces numerator and denominator of the specified unit.
2336 Returns 0 on success, or 1 on unknown unit error.
2337 */
2338
2339int
2340reduceunit(struct unittype *theunit)
2341{
2342 int ret;
2343
2344 if (irreducible)
2345 free(irreducible);
2346 irreducible=0;
2347 ret = DIDREDUCTION;
2348
2349 /* Keep calling reduceproduct until it doesn't do anything */
2350
2351 while (ret & DIDREDUCTION) {
2352 ret = reduceproduct(theunit, 0);
2353 if (!(ret & REDUCTIONERROR))
2354 ret |= reduceproduct(theunit, 1);
2355 if (ret & REDUCTIONERROR){
2356 if (irreducible)
2357 return E_UNKNOWNUNIT;
2358 else
2359 return E_REDUCE;
2360 }
2361 else if (ret & CIRCULARDEF)
2362 return E_CIRCULARDEF;
2363 }
2364 return 0;
2365}
2366
2367/*
2368 Returns one if the argument unit is defined in the data file as a
2369 dimensionless unit. This is determined by comparing its definition to
2370 the string NODIM.
2371*/
2372
2373int
2374ignore_dimless(char *name)
2375{
2376 struct unitlist *ul;
2377 if (!name)
2378 return 0;
2379 ul = ulookup(name);
2380 if (ul && !strcmp(ul->value, NODIM))
2381 return 1;
2382 return 0;
2383}
2384
2385int
2386ignore_nothing(char *name)
2387{
2388 return 0;
2389}
2390
2391
2392int
2393ignore_primitive(char *name)
2394{
2395 struct unitlist *ul;
2396 if (!name)
2397 return 0;
2398 ul = ulookup(name);
2399 if (ul && strchr(ul->value, PRIMITIVECHAR))
2400 return 1;
2401 return 0;
2402}
2403
2404
2405/*
2406 Compare two product lists, return zero if they match and one if
2407 they do not match. They may contain embedded NULLUNITs which are
2408 ignored in the comparison. Units defined as NODIM are also ignored
2409 in the comparison.
2410*/
2411
2412int
2413compareproducts(char **one, char **two, int (*isdimless)(char *name))
2414{
2415 int oneblank, twoblank;
2416 while (*one || *two) {
2417 oneblank = (*one==NULLUNIT) || isdimless(*one);
2418 twoblank = (*two==NULLUNIT) || isdimless(*two);
2419 if (!*one && !twoblank)
2420 return 1;
2421 if (!*two && !oneblank)
2422 return 1;
2423 if (oneblank)
2424 one++;
2425 else if (twoblank)
2426 two++;
2427 else if (strcmp(*one, *two))
2428 return 1;
2429 else
2430 one++, two++;
2431 }
2432 return 0;
2433}
2434
2435
2436/* Return zero if units are compatible, nonzero otherwise. The units
2437 must be reduced, sorted and canceled for this to work. */
2438
2439int
2440compareunits(struct unittype *first, struct unittype *second,
2441 int (*isdimless)(char *name))
2442{
2443 return
2444 compareproducts(first->numerator, second->numerator, isdimless) ||
2445 compareproducts(first->denominator, second->denominator, isdimless);
2446}
2447
2448
2449/* Reduce a unit as much as possible */
2450
2451int
2452completereduce(struct unittype *unit)
2453{
2454 int err;
2455
2456 if ((err=reduceunit(unit)))
2457 return err;
2458
2459 sortunit(unit);
2460 cancelunit(unit);
2461 return 0;
2462}
2463
2464
2465/* Raise theunit to the specified power. This function does not fill
2466 in NULLUNIT gaps, which could be considered a deficiency. */
2467
2468int
2469expunit(struct unittype *theunit, int power)
2470{
2471 char **numptr, **denptr;
2472 double thefactor;
2473 int i, uind, denlen, numlen;
2474
2475 if (power==0){
2476 freeunit(theunit);
2477 initializeunit(theunit);
2478 return 0;
2479 }
2480 numlen=0;
2481 for(numptr=theunit->numerator;*numptr;numptr++) numlen++;
2482 denlen=0;
2483 for(denptr=theunit->denominator;*denptr;denptr++) denlen++;
2484 thefactor=theunit->factor;
2485 for(i=1;i<power;i++){
2486 theunit->factor *= thefactor;
2487 for(uind=0;uind<numlen;uind++){
2488 if (theunit->numerator[uind]!=NULLUNIT){
2489 if (numptr-theunit->numerator>=MAXSUBUNITS-1) {
2490 *numptr=*denptr=0;
2491 return E_PRODOVERFLOW;
2492 }
2493 *numptr++=dupstr(theunit->numerator[uind]);
2494 }
2495 }
2496 for(uind=0;uind<denlen;uind++){
2497 if (theunit->denominator[uind]!=NULLUNIT){
2498 *denptr++=dupstr(theunit->denominator[uind]);
2499 if (denptr-theunit->denominator>=MAXSUBUNITS-1) {
2500 *numptr=*denptr=0;
2501 return E_PRODOVERFLOW;
2502 }
2503 }
2504 }
2505 }
2506 *numptr=0;
2507 *denptr=0;
2508 return 0;
2509}
2510
2511
2512int
2513unit2num(struct unittype *input)
2514{
2515 struct unittype one;
2516 int err;
2517
2518 initializeunit(&one);
2519 if ((err=completereduce(input)))
2520 return err;
2521 if (compareunits(input,&one,ignore_nothing))
2522 return E_NOTANUMBER;
2523 freeunit(input);
2524 return 0;
2525}
2526
2527
2528int
2529unitdimless(struct unittype *input)
2530{
2531 struct unittype one;
2532 initializeunit(&one);
2533 if (compareunits(input, &one, ignore_dimless))
2534 return 0;
2535 freeunit(input); /* Eliminate dimensionless units from list */
2536 return 1;
2537}
2538
2539
2540/*
2541 The unitroot function takes the nth root of an input unit which has
2542 been completely reduced. Returns 1 if the unit is not a power of n.
2543 Input data can contain NULLUNITs.
2544*/
2545
2546int
2547subunitroot(int n,char *current[], char *out[])
2548{
2549 char **ptr;
2550 int count=0;
2551
2552 while(*current==NULLUNIT) current++; /* skip past NULLUNIT entries */
2553 ptr=current;
2554 while(*ptr){
2555 while(*ptr){
2556 if (*ptr!=NULLUNIT){
2557 if (strcmp(*current,*ptr)) break;
2558 count++;
2559 }
2560 ptr++;
2561 }
2562 if (count % n != 0){ /* If not dimensionless generate error, otherwise */
2563 if (!ignore_dimless(*current)) /* just skip over it */
2564 return E_NOTROOT;
2565 } else {
2566 for(count /= n;count>0;count--) *(out++) = dupstr(*current);
2567 }
2568 current=ptr;
2569 }
2570 *out = 0;
2571 return 0;
2572}
2573
2574
2575int
2576rootunit(struct unittype *inunit,int n)
2577{
2578 struct unittype outunit;
2579 int err;
2580
2581 initializeunit(&outunit);
2582 if ((err=completereduce(inunit)))
2583 return err;
2584 /* Roots of negative numbers fail in pow(), even odd roots */
2585 if (inunit->factor < 0)
2586 return E_NOTROOT;
2587 outunit.factor = pow(inunit->factor,1.0/(double)n);
2588 if ((err = subunitroot(n, inunit->numerator, outunit.numerator)))
2589 return err;
2590 if ((err = subunitroot(n, inunit->denominator, outunit.denominator)))
2591 return err;
2592 freeunit(inunit);
2593 initializeunit(inunit);
2594 return multunit(inunit,&outunit);
2595}
2596
2597
2598/* Compute the inverse of a unit (1/theunit) */
2599
2600void
2601invertunit(struct unittype *theunit)
2602{
2603 char **ptr, *swap;
2604 int numlen, length, ind;
2605
2606 theunit->factor = 1.0/theunit->factor;
2607 length=numlen=0;
2608 for(ptr=theunit->denominator;*ptr;ptr++,length++);
2609 for(ptr=theunit->numerator;*ptr;ptr++,numlen++);
2610 if (numlen>length)
2611 length=numlen;
2612 for(ind=0;ind<=length;ind++){
2613 swap = theunit->numerator[ind];
2614 theunit->numerator[ind] = theunit->denominator[ind];
2615 theunit->denominator[ind] = swap;
2616 }
2617}
2618
2619
2620int
2621float2rat(double y, int *p, int *q)
2622{
2623 int coef[20]; /* How long does this buffer need to be? */
2624 int i,termcount,saveq;
2625 double fracpart,x;
2626
2627 /* Compute continued fraction approximation */
2628
2629 x=y;
2630 termcount=0;
2631 while(1){
2632 coef[termcount] = (int) floor(x);
2633 fracpart = x-coef[termcount];
2634 if (fracpart < .001 || termcount==19) break;
2635 x = 1/fracpart;
2636 termcount++;
2637 }
2638
2639 /* Compress continued fraction into rational p/q */
2640
2641 *p=0;
2642 *q=1;
2643 for(i=termcount;i>=1;i--) {
2644 saveq=*q;
2645 *q = coef[i] * *q + *p;
2646 *p = saveq;
2647 }
2648 *p+=*q*coef[0];
2649 return *q<MAXSUBUNITS && fabs((double)*p / (double)*q - y) < DBL_EPSILON;
2650}
2651
2652
2653/* Raise a unit to a power */
2654
2655int
2656unitpower(struct unittype *base, struct unittype *exponent)
2657{
2658 int errcode, p, q;
2659
2660 errcode = unit2num(exponent);
2661 if (errcode == E_NOTANUMBER)
2662 return E_DIMEXPONENT;
2663 if (errcode)
2664 return errcode;
2665 errcode = unit2num(base);
2666 if (!errcode){ /* Exponent base is dimensionless */
2667 base->factor = pow(base->factor,exponent->factor);
2668 if (errno)
2669 return E_FUNC;
2670 }
2671 else if (errcode==E_NOTANUMBER) { /* Base not dimensionless */
2672 if (!float2rat(exponent->factor,&p,&q)){ /* Exponent must be rational */
2673 if (unitdimless(base)){
2674 base->factor = pow(base->factor,exponent->factor);
2675 if (errno)
2676 return E_FUNC;
2677 }
2678 else
2679 return E_IRRATIONAL_EXPONENT;
2680 } else {
2681 if (q!=1) {
2682 errcode = rootunit(base, q);
2683 if (errcode == E_NOTROOT)
2684 return E_BASE_NOTROOT;
2685 if (errcode)
2686 return errcode;
2687 }
2688 errcode = expunit(base, abs(p));
2689 if (errcode)
2690 return errcode;
2691 if (p<0)
2692 invertunit(base);
2693 }
2694 }
2695 else return errcode;
2696 return 0;
2697}
2698
2699
2700/* Old units program would give message about what each operand
2701 reduced to, showing that they weren't conformable. Can this
2702 be achieved here? */
2703
2704int
2705addunit(struct unittype *unita, struct unittype *unitb)
2706{
2707 int err;
2708
2709 if ((err=completereduce(unita)))
2710 return err;
2711 if ((err=completereduce(unitb)))
2712 return err;
2713 if (compareunits(unita,unitb,ignore_nothing))
2714 return E_BADSUM;
2715 unita->factor += unitb->factor;
2716 freeunit(unitb);
2717 return 0;
2718}
2719
2720
2721double
2722linearinterp(double a, double b, double aval, double bval, double c)
2723{
2724 double lambda;
2725
2726 lambda = (b-c)/(b-a);
2727 return lambda*aval + (1-lambda)*bval;
2728}
2729
2730
2731/* evaluate a user function */
2732
2733#define INVERSE 1
2734#define FUNCTION 0
2735#define ALLERR 1
2736#define NORMALERR 0
2737
2738int
2739evalfunc(struct unittype *theunit, struct func *infunc, int inverse,
2740 int allerrors)
2741{
2742 struct unittype result;
2743 struct functype *thefunc;
2744 int err;
2745 double value;
2746 int foundit, count;
2747 struct unittype *save_value;
2748 char *save_function;
2749
2750 if (infunc->table) { /* Tables are short, so use dumb search algorithm */
2751 err = parseunit(&result, infunc->tableunit, 0, 0);
2752 if (err)
2753 return E_BADFUNCDIMEN;
2754 if (inverse){
2755 err = divunit(theunit, &result);
2756 if (err)
2757 return err;
2758 err = unit2num(theunit);
2759 if (err==E_NOTANUMBER)
2760 return E_BADFUNCARG;
2761 if (err)
2762 return err;
2763 value = theunit->factor;
2764 foundit=0;
2765 for(count=0;count<infunc->tablelen-1;count++)
2766 if ((infunc->table[count].value<=value &&
2767 value<=infunc->table[count+1].value) ||
2768 (infunc->table[count+1].value<=value &&
2769 value<=infunc->table[count].value)){
2770 foundit=1;
2771 value = linearinterp(infunc->table[count].value,
2772 infunc->table[count+1].value,
2773 infunc->table[count].location,
2774 infunc->table[count+1].location,
2775 value);
2776 break;
2777 }
2778 if (!foundit)
2779 return E_NOTINDOMAIN;
2780 freeunit(&result);
2781 freeunit(theunit);
2782 theunit->factor = value;
2783 return 0;
2784 } else {
2785 err=unit2num(theunit);
2786 if (err)
2787 return err;
2788 value=theunit->factor;
2789 foundit=0;
2790 for(count=0;count<infunc->tablelen-1;count++)
2791 if (infunc->table[count].location<=value &&
2792 value<=infunc->table[count+1].location){
2793 foundit=1;
2794 value = linearinterp(infunc->table[count].location,
2795 infunc->table[count+1].location,
2796 infunc->table[count].value,
2797 infunc->table[count+1].value,
2798 value);
2799 break;
2800 }
2801 if (!foundit)
2802 return E_NOTINDOMAIN;
2803 result.factor *= value;
2804 }
2805 } else { /* it's a function */
2806 if (inverse){
2807 thefunc=&(infunc->inverse);
2808 if (!thefunc->def)
2809 return E_NOINVERSE;
2810 }
2811 else
2812 thefunc=&(infunc->forward);
2813 err = completereduce(theunit);
2814 if (err)
2815 return err;
2816 if (thefunc->dimen){
2817 err = parseunit(&result, thefunc->dimen, 0, 0);
2818 if (err)
2819 return E_BADFUNCDIMEN;
2820 err = completereduce(&result);
2821 if (err)
2822 return E_BADFUNCDIMEN;
2823 if (compareunits(&result, theunit, ignore_nothing))
2824 return E_BADFUNCARG;
2825 value = theunit->factor/result.factor;
2826 } else
2827 value = theunit->factor;
2828 if (thefunc->domain_max &&
2829 (value > *thefunc->domain_max ||
2830 (thefunc->domain_max_open && value == *thefunc->domain_max)))
2831 return E_NOTINDOMAIN;
2832 if (thefunc->domain_min &&
2833 (value < *thefunc->domain_min ||
2834 (thefunc->domain_min_open && value == *thefunc->domain_min)))
2835 return E_NOTINDOMAIN;
2836 save_value = parameter_value;
2837 save_function = function_parameter;
2838 parameter_value = theunit;
2839 function_parameter = thefunc->param;
2840 err = parseunit(&result, thefunc->def, 0,0);
2841 function_parameter = save_function;
2842 parameter_value = save_value;
2843 if (err && (allerrors == ALLERR || err==E_PARSEMEM || err==E_PRODOVERFLOW
2844 || err==E_NOTROOT || err==E_BADFUNCTYPE))
2845 return err;
2846 if (err)
2847 return E_FUNARGDEF;
2848 }
2849 freeunit(theunit);
2850 initializeunit(theunit);
2851 multunit(theunit, &result);
2852 return 0;
2853}
2854
2855
2856/*
2857 append a formatted string to a buffer; first character of buffer
2858 should be '\0' on first call
2859*/
2860size_t
2861vbufprintf(char **buf, size_t *bufsize, char *fmt, ...)
2862{
2863 va_list args;
2864 size_t
2865 oldlen, /* length of current buffer contents */
2866 newlen, /* length of string to be added */
2867 buflen; /* length of new buffer contents */
2868 double growfactor = 1.5;
2869 static char *newbuf = NULL;
2870 char *oldbuf;
2871
2872 oldlen = strlen(*buf);
2873 oldbuf = dupstr(*buf);
2874
2875 /* get length of formatted string to be appended to buffer */
2876 va_start(args, fmt);
2877 newlen = vsnprintf(NULL, 0, fmt, args);
2878 va_end(args);
2879
2880 /* allocate a buffer for the new string */
2881 newbuf = (char *) malloc(newlen + 1);
2882 if (newbuf == NULL) {
2883 fprintf(stderr, "%s (bufprintf): memory allocation error\n", progname);
2884 exit(EXIT_FAILURE);
2885 }
2886
2887 /* expand main buffer if necessary */
2888 if (*bufsize < oldlen + newlen + 1) {
2889 *bufsize = (size_t) ((oldlen + newlen) * growfactor + 1);
2890
2891 *buf = (char *) realloc(*buf, *bufsize);
2892 if (*buf == NULL) {
2893 fprintf(stderr, "%s (bufprintf): memory allocation error\n", progname);
2894 exit(EXIT_FAILURE);
2895 }
2896 }
2897
2898 /* generate the new formatted string */
2899 va_start(args, fmt);
2900 newlen = vsprintf(newbuf, fmt, args);
2901 va_end(args);
2902
2903 /* copy old and new strings to buffer */
2904 strcpy(*buf, oldbuf);
2905 strcat(*buf, newbuf);
2906 buflen = strlen(*buf);
2907
2908 free(oldbuf);
2909 free(newbuf);
2910
2911 return buflen;
2912}
2913
2914
2915/*
2916 similar to showunit(), but it saves the string in a buffer rather
2917 than sending it to an output stream
2918*/
2919char *
2920buildunitstr(struct unittype *theunit)
2921{
2922 char **ptr;
2923 char *buf;
2924 int printedslash;
2925 int counter = 1;
2926 size_t bufsize, newlen;
2927
2928 bufsize = 256;
2929 buf = (char *) mymalloc(bufsize, "(buildunitstr)");
2930 *buf = '\0';
2931
2932 /* factor */
2933 newlen = vbufprintf(&buf, &bufsize, num_format.format, theunit->factor);
2934
2935 /* numerator */
2936 for (ptr = theunit->numerator; *ptr; ptr++) {
2937 if (ptr > theunit->numerator && **ptr &&
2938 !strcmp(*ptr, *(ptr - 1)))
2939 counter++;
2940 else {
2941 if (counter > 1)
2942 newlen = vbufprintf(&buf, &bufsize, "%s%d", powerstring, counter);
2943 if (**ptr)
2944 newlen = vbufprintf(&buf, &bufsize, " %s", *ptr);
2945 counter = 1;
2946 }
2947 }
2948 if (counter > 1)
2949 newlen = vbufprintf(&buf, &bufsize, "%s%d", powerstring, counter);
2950 counter = 1;
2951 printedslash = 0;
2952
2953 /* denominator */
2954 for (ptr = theunit->denominator; *ptr; ptr++) {
2955 if (ptr > theunit->denominator && **ptr &&
2956 !strcmp(*ptr, *(ptr - 1)))
2957 counter++;
2958 else {
2959 if (counter > 1)
2960 newlen = vbufprintf(&buf, &bufsize, "%s%d", powerstring, counter);
2961 if (**ptr) {
2962 if (!printedslash)
2963 newlen = vbufprintf(&buf, &bufsize, " /");
2964 printedslash = 1;
2965 newlen = vbufprintf(&buf, &bufsize, " %s", *ptr);
2966 }
2967 counter = 1;
2968 }
2969 }
2970 if (counter > 1)
2971 newlen = vbufprintf(&buf, &bufsize, "%s%d", powerstring, counter);
2972
2973 return buf;
2974}
2975
2976/*
2977 If the given character string has only one unit name in it, then print out
2978 the rule for that unit. In any case, print out the reduced form for
2979 the unit.
2980*/
2981
2982void
2983showdefinition(char *unitstr, struct unittype *theunit)
2984{
2985 size_t bufsize = 256;
2986 char *buf, *showstr;
2987
2988 logputs(deftext);
2989 showstr = buildunitstr(theunit);
2990 buf = (char *) mymalloc(bufsize, "(showdefinition)");
2991
2992 while((unitstr = lookupunit(unitstr,1))
2993 && strspn(unitstr,digits) != strlen(unitstr)
2994 && !strchr(unitstr,PRIMITIVECHAR)) {
2995 if (strlen(unitstr) > bufsize - 1) {
2996 bufsize = strlen(unitstr) + 1;
2997 buf = realloc(buf, bufsize);
2998 }
2999 tightbufprint(buf, unitstr);
3000 logputs(buf);
3001 if (strcmp(buf, showstr))
3002 logputs(" = ");
3003 }
3004
3005 if (strcmp(buf, showstr))
3006 logputs(showstr);
3007 logputchar('\n');
3008 free(buf);
3009 free(showstr);
3010}
3011
3012
3013void
3014showfunction(struct functype *func)
3015{
3016 struct unittype unit;
3017 int not_dimensionless, i;
3018
3019 if (!func->def) {
3020 logputs(" is undefined");
3021 return;
3022 }
3023
3024 if (func->dimen){ /* coverity[check_return] */
3025 parseunit(&unit,func->dimen,0,0); /* unit2num returns 0 for */
3026 not_dimensionless = unit2num(&unit); /* dimensionless units */
3027 }
3028
3029 logprintf("(%s) = %s", func->param, func->def);
3030 if (func->domain_min || func->domain_max){
3031 logputchar('\n');
3032 for(i=strwidth(deftext);i;i--) logputchar(' ');
3033 logputs("defined for ");
3034 if (func->domain_min && func->domain_max) {
3035 logprintf(num_format.format, *func->domain_min);
3036 if (func->dimen && (not_dimensionless || unit.factor != 1)){
3037 if (isdecimal(*func->dimen))
3038 logputs(" *");
3039 logprintf(" %s",func->dimen);
3040 }
3041 logputs(func->domain_min_open?" < ":" <= ");
3042 }
3043 logputs(func->param);
3044 if (func->domain_max){
3045 logputs(func->domain_max_open?" < ":" <= ");
3046 logprintf(num_format.format, *func->domain_max);
3047 }
3048 else {
3049 logputs(func->domain_min_open?" > ":" >= ");
3050 logprintf(num_format.format, *func->domain_min);
3051 }
3052 if (func->dimen && (not_dimensionless || unit.factor != 1)){
3053 if (isdecimal(*func->dimen))
3054 logputs(" *");
3055 logprintf(" %s",func->dimen);
3056 }
3057 if (!func->dimen) logputs(" (any units)");
3058 } else if (func->dimen){
3059 logputchar('\n');
3060 for(i=strwidth(deftext);i;i--) logputchar(' ');
3061 if (not_dimensionless)
3062 logprintf("%s has units %s",func->param, func->dimen);
3063 else
3064 logprintf("%s is dimensionless",func->param);
3065 }
3066 logputchar('\n');
3067}
3068
3069void
3070showtable(struct func *fun, int inverse)
3071{
3072 int i;
3073
3074 logprintf("%sinterpolated table with points\n",deftext);
3075 if (inverse){
3076 int reverse, j;
3077 reverse = (fun->table[0].value > fun->table[fun->tablelen-1].value);
3078 for(i=0;i<fun->tablelen;i++){
3079 if (reverse) j = fun->tablelen-i-1;
3080 else j=i;
3081 if (flags.verbose>0)
3082 logputs("\t\t ");
3083 logprintf("~%s(", fun->name);
3084 logprintf(num_format.format, fun->table[j].value);
3085 if (isdecimal(fun->tableunit[0]))
3086 logputs(" *");
3087 logprintf(" %s",fun->tableunit);
3088 logputs(") = ");
3089 logprintf(num_format.format, fun->table[j].location);
3090 logputchar('\n');
3091 }
3092 } else {
3093 for(i=0;i<fun->tablelen;i++){
3094 if (flags.verbose>0)
3095 logputs("\t\t ");
3096 logprintf("%s(", fun->name);
3097 logprintf(num_format.format, fun->table[i].location);
3098 logputs(") = ");
3099 logprintf(num_format.format, fun->table[i].value);
3100 if (isdecimal(fun->tableunit[0]))
3101 logputs(" *");
3102 logprintf(" %s\n",fun->tableunit);
3103 }
3104 }
3105}
3106
3107
3108void
3109showfuncdefinition(struct func *fun, int inverse)
3110{
3111 if (fun->table) /* It's a table */
3112 showtable(fun, inverse);
3113 else {
3114 logprintf("%s%s%s", deftext,inverse?"~":"", fun->name);
3115 if (inverse)
3116 showfunction(&fun->inverse);
3117 else
3118 showfunction(&fun->forward);
3119 }
3120}
3121
3122
3123void
3124showunitlistdef(struct wantalias *alias)
3125{
3126 logprintf("%sunit list, ",deftext);
3127 tightprint(stdout,alias->definition);
3128 if (logfile) tightprint(logfile,alias->definition);
3129 logputchar('\n');
3130}
3131
3132
3133/* Show conversion to a function. Input unit 'have' is replaced by the
3134 function inverse and completely reduced. */
3135
3136int
3137showfunc(char *havestr, struct unittype *have, struct func *fun)
3138{
3139 int err;
3140 char *dimen;
3141
3142 err = evalfunc(have, fun, INVERSE, NORMALERR);
3143 if (!err)
3144 err = completereduce(have);
3145 if (err) {
3146 if (err==E_BADFUNCARG){
3147 logputs("conformability error");
3148 if (fun->table)
3149 dimen = fun->tableunit;
3150 else if (fun->inverse.dimen)
3151 dimen = fun->inverse.dimen;
3152 else
3153 dimen = 0;
3154 if (!dimen)
3155 logputchar('\n');
3156 else {
3157 struct unittype want;
3158
3159 if (emptystr(dimen))
3160 dimen = "1";
3161 logprintf(": conversion requires dimensions of '%s'\n",dimen);
3162 if (flags.verbose==2) logprintf("\t%s = ",havestr);
3163 else if (flags.verbose==1) logputchar('\t');
3164 showunit(have);
3165 if (flags.verbose==2) logprintf("\n\t%s = ",dimen);
3166 else if (flags.verbose==1) logprintf("\n\t");
3167 else logputchar('\n'); /* coverity[check_return] */
3168 parseunit(&want, dimen, 0, 0); /* coverity[check_return] */
3169 completereduce(&want); /* dimen was already checked for */
3170 showunit(&want); /* errors so no need to check here */
3171 logputchar('\n');
3172 }
3173 } else if (err==E_NOTINDOMAIN)
3174 logprintf("Value '%s' is not in the function's range\n",havestr);
3175 else if (err==E_NOINVERSE)
3176 logprintf("Inverse of the function '%s' is not defined\n",fun->name);
3177 else
3178 logputs("Function evaluation error (bad function definition)\n");
3179 return 1;
3180 }
3181 if (flags.verbose==2)
3182 logprintf("\t%s = %s(", havestr, fun->name);
3183 else if (flags.verbose==1)
3184 logputchar('\t');
3185 showunit(have);
3186 if (flags.verbose==2)
3187 logputchar(')');
3188 logputchar('\n');
3189 return 0;
3190}
3191
3192/* Print the conformability error message */
3193
3194void
3195showconformabilityerr(char *havestr,struct unittype *have,
3196 char *wantstr,struct unittype *want)
3197{
3198 logputs("conformability error\n");
3199 if (flags.verbose==2)
3200 logprintf("\t%s = ",havestr);
3201 else if (flags.verbose==1)
3202 logputchar('\t');
3203 showunit(have);
3204 if (flags.verbose==2)
3205 logprintf("\n\t%s = ",wantstr);
3206 else if (flags.verbose==1)
3207 logputs("\n\t");
3208 else
3209 logputchar('\n');
3210 showunit(want);
3211 logputchar('\n');
3212} /* end showconformabilityerr */
3213
3214
3215
3216/*
3217 determine whether a unit string begins with a fraction; assume it
3218 does if it starts with an integer, '|', and another integer
3219*/
3220int
3221isfract(const char *unitstr)
3222{
3223 char *enddouble=0, *endlong=0;
3224
3225 while (isdigit(*unitstr))
3226 unitstr++;
3227 if (*unitstr++ == '|') {
3228 (void)strtod(unitstr, &enddouble);
3229 (void)strtol(unitstr, &endlong, 10);
3230 if (enddouble == endlong)
3231 return 1;
3232 }
3233 return 0;
3234}
3235
3236int
3237checksigdigits(char *arg)
3238{
3239 int errors, ival;
3240 char *nonum;
3241
3242 errors = 0;
3243
3244 if (!strcmp(arg, "max"))
3245 num_format.precision = MAXPRECISION;
3246 else {
3247 ival = (int) strtol(arg, &nonum, 10);
3248 if (!emptystr(nonum)) {
3249 fprintf(stderr,
3250 "%s: invalid significant digits (%s)--integer value or 'max' required\n",
3251 progname, arg);
3252 errors++;
3253 }
3254 else if (ival <= 0) {
3255 fprintf(stderr, "%s: number of significant digits must be positive\n",
3256 progname);
3257 errors++;
3258 }
3259 else if (ival > MAXPRECISION) {
3260 fprintf(stderr,
3261 "%s: too many significant digits (%d)--using maximum value (%d)\n",
3262 progname, ival, MAXPRECISION);
3263 num_format.precision = MAXPRECISION;
3264 }
3265 else
3266 num_format.precision = ival;
3267 }
3268 if (errors)
3269 return -1;
3270 else
3271 return 0;
3272}
3273
3274/* set output number format specification from significant digits and type */
3275
3276int
3277setnumformat()
3278{
3279 size_t len;
3280
3281 if (strchr("Ee", num_format.type))
3282 num_format.precision--;
3283
3284 len = 4; /* %, decimal point, type, terminating NUL */
3285 if (num_format.precision > 0)
3286 len += (size_t) floor(log10((double) num_format.precision))+1;
3287 num_format.format = (char *) mymalloc(len, "(setnumformat)");
3288 sprintf(num_format.format, "%%.%d%c", num_format.precision, num_format.type);
3289 return 0;
3290}
3291
3292/*
3293 parse and validate the output number format specification and
3294 extract its type and precision into the num_format structure.
3295 Returns nonzero for invalid format.
3296*/
3297
3298int
3299parsenumformat()
3300{
3301 static char *format_types = NULL;
3302 static char *format_flags = "+-# 0'";
3303 static char badflag;
3304 char *two = "0x1p+1";
3305 char *valid="ABCDEFGHIJKLMNOPQRSTUVWXYXabcdefghijklmnopqrstuvwxyx.01234567890";
3306 char *dotptr, *lptr, *nonum, *p;
3307 char testbuf[80];
3308 int errors, ndx;
3309
3310 if (format_types == NULL){
3311 format_types = (char *) mymalloc(strlen(BASE_FORMATS)+4, "(parsenumformat)");
3312 strcpy(format_types,BASE_FORMATS);
3313
3314 /* check for support of type 'F' (MS VS 2012 doesn't have it) */
3315 sprintf(testbuf, "%.1F", 1.2);
3316 if (strlen(testbuf) == 3 && testbuf[0] == '1' && testbuf[2] == '2')
3317 strcat(format_types,"F");
3318
3319 /* check for support of hexadecimal floating point */
3320 sprintf(testbuf, "%.0a", 2.0);
3321 if (!strcmp(testbuf,two))
3322 strcat(format_types, "aA");
3323
3324 /* check for support of digit-grouping (') flag */
3325 sprintf(testbuf, "%'.0f", 1234.0);
3326 if (strlen(testbuf) > 2 && testbuf[0] == '1' && testbuf[2] == '2')
3327 badflag = '\0'; /* supported */
3328 else
3329 badflag = '\''; /* not supported */
3330 }
3331
3332 errors = 0;
3333
3334 p = num_format.format;
3335
3336 if (*p != '%') {
3337 fprintf(stderr, "%s: number format specification must start with '%%'\n",
3338 progname);
3339 errors++;
3340 }
3341 else if (strrchr(num_format.format, '%') != num_format.format) {
3342 fprintf(stderr, "%s: only one '%%' allowed in number format specification\n",
3343 progname);
3344 errors++;
3345 p++;
3346 }
3347 else
3348 p++;
3349
3350 dotptr = strchr(num_format.format, '.');
3351 if (dotptr && strrchr(num_format.format, '.') != dotptr) {
3352 fprintf(stderr, "%s: only one '.' allowed in number format specification\n",
3353 progname);
3354 errors++;
3355 }
3356
3357 /* skip over flags */
3358 while (*p && strchr(format_flags, *p)) {
3359 if (*p == badflag) { /* only if digit-grouping flag (') not supported */
3360 fprintf(stderr, "%s: digit-grouping flag (') not supported\n", progname);
3361 errors++;
3362 }
3363 p++;
3364 }
3365
3366 /* check for type length modifiers, which aren't allowed */
3367 if ((lptr = strstr(num_format.format, "hh"))
3368 || (lptr = strstr(num_format.format, "ll"))) {
3369 fprintf(stderr, "%s: type length modifier (%.2s) not supported\n", progname, lptr);
3370 errors++;
3371 }
3372 else if ((lptr = strpbrk(num_format.format, "hjLltz"))) {
3373 fprintf(stderr, "%s: type length modifier (%c) not supported\n", progname, lptr[0]);
3374 errors++;
3375 }
3376
3377 /* check for other invalid characters */
3378 ndx = strspn(p, valid);
3379 if (ndx < strlen(p)) {
3380 fprintf(stderr, "%s: invalid character (%c) in width, precision, or type\n",
3381 progname, p[ndx]);
3382 errors++;
3383 }
3384
3385 if (errors) { /* results of any other checks are likely to be misleading */
3386 fprintf(stderr, "%s: invalid number format specification (%s)\n",
3387 progname, num_format.format);
3388 fprintf(stderr, "%s: valid specification is %%[flags][width][.precision]type\n",
3389 progname);
3390 return -1;
3391 }
3392
3393 /* get width and precision if specified; check precision */
3394 num_format.width = (int) strtol(p, &nonum, 10);
3395
3396 if (*nonum == '.'){
3397 if (isdigit(nonum[1]))
3398 num_format.precision = (int) strtol(nonum+1, &nonum, 10);
3399 else {
3400 num_format.precision = 0;
3401 nonum++;
3402 }
3403 }
3404 else /* precision not given--use printf() default */
3405 num_format.precision = 6;
3406
3407 /* check for valid format type */
3408 if (emptystr(nonum)) {
3409 fprintf(stderr, "%s: missing format type\n", progname);
3410 errors++;
3411 }
3412 else {
3413 if (strchr(format_types, *nonum)) {
3414 if (nonum[1]) {
3415 fprintf(stderr, "%s: invalid character(s) (%s) after format type\n",
3416 progname, nonum + 1);
3417 errors++;
3418 }
3419 else
3420 num_format.type = *nonum;
3421 }
3422 else {
3423 fprintf(stderr,
3424 "%s: invalid format type (%c)--valid types are [%s]\n",
3425 progname, *nonum, format_types);
3426 errors++;
3427 }
3428 }
3429
3430 if (num_format.precision == 0 &&
3431 (num_format.type == 'G' || num_format.type == 'g'))
3432 num_format.precision = 1;
3433
3434 if (errors) {
3435 fprintf(stderr, "%s: invalid number format specification (%s)\n",
3436 progname, num_format.format);
3437 fprintf(stderr, "%s: valid specification is %%[flags][width][.precision]type\n",
3438 progname);
3439 return -1;
3440 }
3441 else
3442 return 0;
3443}
3444
3445/*
3446 round a number to the lesser of the displayed precision or the
3447 remaining significant digits; indicate in hasnondigits if a number
3448 will contain any character other than the digits 0-9 in the current
3449 display format.
3450*/
3451
3452double
3453round_output(double value, int sigdigits, int *hasnondigits)
3454{
3455 int buflen;
3456 char *buf;
3457 double rounded;
3458 double mult_factor, rdigits;
3459 int fmt_digits; /* decimal significant digits in format */
3460
3461 if (!isfinite(value)){
3462 if (hasnondigits)
3463 *hasnondigits = 1;
3464 return value;
3465 }
3466
3467 fmt_digits = num_format.precision;
3468 switch (num_format.type) {
3469 case 'A':
3470 case 'a':
3471 sigdigits = round(sigdigits * log2(10) / 4);
3472 fmt_digits++;
3473 break;
3474 case 'E':
3475 case 'e':
3476 fmt_digits++;
3477 break;
3478 case 'F':
3479 case 'f':
3480 if (fabs(value) > 0)
3481 fmt_digits += ceil(log10(fabs(value)));
3482 break;
3483 }
3484
3485 if (sigdigits < fmt_digits)
3486 rdigits = sigdigits;
3487 else
3488 rdigits = fmt_digits;
3489
3490 /* place all significant digits to the right of the radix */
3491 if (value != 0)
3492 rdigits -= ceil(log10(fabs(value)));
3493 /* then handle like rounding to specified decimal places */
3494 mult_factor = pow(10.0, rdigits);
3495 rounded = round(value * mult_factor) / mult_factor;
3496
3497 /* allow for sign (1), radix (1), exponent (5), E or E formats (1), NUL */
3498 buflen = num_format.precision + 9;
3499
3500 if (num_format.width > buflen)
3501 buflen = num_format.width;
3502
3503 if (strchr("Ff", num_format.type)) {
3504 int len=num_format.precision+2;
3505 if (fabs(value) > 1.0)
3506 len += (int) floor(log10(fabs(value))) + 1;
3507 if (len > buflen)
3508 buflen = len;
3509 }
3510
3511 /* allocate space for thousands separators with digit-grouping (') flag */
3512 /* assume worst case--that all groups are two digits */
3513 if (strchr(num_format.format, '\'') && strchr("FfGg", num_format.type))
3514 buflen = buflen*3/2;
3515
3516 buf = (char *) mymalloc(buflen, "(round_output)");
3517 sprintf(buf, num_format.format, value);
3518
3519 if (hasnondigits){
3520 if (strspn(buf, "1234567890") != strlen(buf))
3521 *hasnondigits = 1;
3522 else
3523 *hasnondigits = 0;
3524 }
3525
3526 free(buf);
3527 return rounded;
3528}
3529
3530/*
3531 Determine significant digits in remainder relative to an original
3532 value which is assumed to have full double precision. Returns
3533 the number of binary or decimal digits depending on the value
3534 of base, which must be 2 or 10.
3535*/
3536
3537int
3538getsigdigits(double original, double remainder, int base)
3539{
3540 int sigdigits;
3541 double maxdigits;
3542 double (*logfunc)(double);
3543
3544 if (base == 2) {
3545 maxdigits = DBL_MANT_DIG;
3546 logfunc = log2;
3547 }
3548 else {
3549 maxdigits = DBL_MANT_DIG * log10(2.0);
3550 logfunc = log10;
3551 }
3552
3553 if (original == 0)
3554 return floor(maxdigits);
3555 else if (remainder == 0)
3556 return 0;
3557
3558 sigdigits = floor(maxdigits - logfunc(fabs(original/remainder)));
3559
3560 if (sigdigits < 0)
3561 sigdigits = 0;
3562
3563 return sigdigits;
3564}
3565
3566/* Rounds a double to the specified number of binary or decimal
3567 digits. The base argument must be 2 or 10. */
3568
3569double
3570round_digits(double value, int digits, int base)
3571{
3572 double mult_factor;
3573 double (*logfunc)(double);
3574
3575 if (digits == 0)
3576 return 0.0;
3577
3578 if (base == 2)
3579 logfunc = log2;
3580 else
3581 logfunc = log10;
3582
3583 if (value != 0)
3584 digits -= ceil(logfunc(fabs(value)));
3585
3586 mult_factor = pow((double) base, digits);
3587
3588 return round(value*mult_factor)/mult_factor;
3589}
3590
3591
3592/* Returns true if the value will display as equal to the reference
3593 and if hasnondigits is non-null then return true if the displayed
3594 output contains any character in "0123456789". */
3595
3596int
3597displays_as(double reference, double value, int *hasnondigits)
3598{
3599 int buflen;
3600 char *buf;
3601 double rounded;
3602
3603 if (!isfinite(value)){
3604 if (hasnondigits)
3605 *hasnondigits = 1;
3606 return 0;
3607 }
3608
3609 /* allow for sign (1), radix (1), exponent (5), E or E formats (1), NUL */
3610 buflen = num_format.precision + 9;
3611
3612 if (num_format.width > buflen)
3613 buflen = num_format.width;
3614
3615 if (strchr("Ff", num_format.type)) {
3616 int len=num_format.precision+2;
3617 if (fabs(value) > 1.0)
3618 len += (int) floor(log10(fabs(value))) + 1;
3619 if (len > buflen)
3620 buflen = len;
3621 }
3622
3623 /* allocate space for thousands separators with digit-grouping (') flag */
3624 /* assume worst case--that all groups are two digits */
3625 if (strchr(num_format.format, '\'') && strchr("FfGg", num_format.type))
3626 buflen = buflen*3/2;
3627
3628 buf = (char *) mymalloc(buflen, "(round_to_displayed)");
3629 sprintf(buf, num_format.format, value);
3630
3631 if (hasnondigits){
3632 if (strspn(buf, "1234567890") != strlen(buf))
3633 *hasnondigits = 1;
3634 else
3635 *hasnondigits = 0;
3636 }
3637 rounded = strtod(buf, NULL);
3638 free(buf);
3639
3640 return rounded==reference;
3641}
3642
3643
3644
3645/* Print the unit in 'unitstr' along with any necessary punctuation.
3646 The 'value' is the multiplier for the unit. If printnum is set
3647 to PRINTNUM then this value is printed, or set it to NOPRINTNUM
3648 to prevent the value from being printed.
3649*/
3650
3651#define PRINTNUM 1
3652#define NOPRINTNUM 0
3653
3654void
3655showunitname(double value, char *unitstr, int printnum)
3656{
3657 int hasnondigits; /* flag to indicate nondigits in displayed value */
3658 int is_one; /* Does the value display as 1? */
3659
3660 is_one = displays_as(1, value, &hasnondigits);
3661
3662 if (printnum && !(is_one && isdecimal(*unitstr)))
3663 logprintf(num_format.format, value);
3664
3665 if (strpbrk(unitstr, "+-")) /* show sums and differences of units */
3666 logprintf(" (%s)", unitstr); /* in parens */
3667 /* fractional unit 1|x and multiplier is all digits and not one-- */
3668 /* no space or asterisk or numerator (3|8 in instead of 3 * 1|8 in) */
3669 else if (printnum && !flags.showfactor
3670 && startswith(unitstr,"1|") && isfract(unitstr)
3671 && !is_one && !hasnondigits)
3672 logputs(unitstr+1);
3673 /* multiplier is unity and unit begins with a number--no space or */
3674 /* asterisk (multiplier was not shown, and the space was already output)*/
3675 else if (is_one && isdecimal(*unitstr))
3676 logputs(unitstr);
3677 /* unitstr begins with a non-fraction number and multiplier was */
3678 /* shown--prefix a spaced asterisk */
3679 else if (isdecimal(unitstr[0]))
3680 logprintf(" * %s", unitstr);
3681 else
3682 logprintf(" %s", unitstr);
3683}
3684
3685
3686/* Show the conversion factors or print the conformability error message */
3687
3688int
3689showanswer(char *havestr,struct unittype *have,
3690 char *wantstr,struct unittype *want)
3691{
3692 struct unittype invhave;
3693 int doingrec; /* reciprocal conversion? */
3694 char *right = NULL, *left = NULL;
3695
3696 doingrec=0;
3697 if (compareunits(have, want, ignore_dimless)) {
3698 char **src,**dest;
3699
3700 invhave.factor=1/have->factor;
3701 for(src=have->numerator,dest=invhave.denominator;*src;src++,dest++)
3702 *dest=*src;
3703 *dest=0;
3704 for(src=have->denominator,dest=invhave.numerator;*src;src++,dest++)
3705 *dest=*src;
3706 *dest=0;
3707 if (flags.strictconvert || compareunits(&invhave, want, ignore_dimless)){
3708 showconformabilityerr(havestr, have, wantstr, want);
3709 return -1;
3710 }
3711 if (flags.verbose>0)
3712 logputchar('\t');
3713 logputs("reciprocal conversion\n");
3714 have=&invhave;
3715 doingrec=1;
3716 }
3717 if (flags.verbose==2) {
3718 if (!doingrec)
3719 left=right="";
3720 else if (strchr(havestr,'/')) {
3721 left="1 / (";
3722 right=")";
3723 } else {
3724 left="1 / ";
3725 right="";
3726 }
3727 }
3728
3729 /* Print the first line of output. */
3730
3731 if (flags.verbose==2)
3732 logprintf("\t%s%s%s = ",left,havestr,right);
3733 else if (flags.verbose==1)
3734 logputs("\t* ");
3735 if (flags.verbose==2)
3736 showunitname(have->factor / want->factor, wantstr, PRINTNUM);
3737 else
3738 logprintf(num_format.format, have->factor / want->factor);
3739
3740 /* Print the second line of output. */
3741
3742 if (!flags.oneline){
3743 if (flags.verbose==2)
3744 logprintf("\n\t%s%s%s = (1 / ",left,havestr,right);
3745 else if (flags.verbose==1)
3746 logputs("\n\t/ ");
3747 else
3748 logputchar('\n');
3749 logprintf(num_format.format, want->factor / have->factor);
3750 if (flags.verbose==2) {
3751 logputchar(')');
3752 showunitname(0,wantstr, NOPRINTNUM);
3753 }
3754 }
3755 logputchar('\n');
3756 return 0;
3757}
3758
3759
3760/* Checks that the function definition has a valid inverse
3761 Prints a message to stdout if function has bad definition or
3762 invalid inverse.
3763*/
3764
3765#define SIGN(x) ( (x) > 0.0 ? 1 : \
3766 ( (x) < 0.0 ? (-1) : \
3767 0 ))
3768
3769void
3770checkfunc(struct func *infunc, int verbose)
3771{
3772 struct unittype theunit, saveunit;
3773 struct prefixlist *prefix;
3774 int err, i;
3775 double direction;
3776
3777 if (infunc->skip_error_check){
3778 if (verbose)
3779 printf("skipped function '%s'\n", infunc->name);
3780 return;
3781 }
3782 if (verbose)
3783 printf("doing function '%s'\n", infunc->name);
3784 if ((prefix=plookup(infunc->name))
3785 && strlen(prefix->name)==strlen(infunc->name))
3786 printf("Warning: '%s' defined as prefix and function\n",infunc->name);
3787 if (infunc->table){
3788 /* Check for valid unit for the table */
3789 if (parseunit(&theunit, infunc->tableunit, 0, 0) ||
3790 completereduce(&theunit))
3791 printf("Table '%s' has invalid units '%s'\n",
3792 infunc->name, infunc->tableunit);
3793 freeunit(&theunit);
3794
3795 /* Check for monotonicity which is needed for unique inverses */
3796 if (infunc->tablelen<=1){
3797 printf("Table '%s' has only one data point\n", infunc->name);
3798 return;
3799 }
3800 direction = SIGN(infunc->table[1].value - infunc->table[0].value);
3801 for(i=2;i<infunc->tablelen;i++)
3802 if (SIGN(infunc->table[i].value-infunc->table[i-1].value) != direction){
3803 printf("Table '%s' lacks unique inverse around entry " ERRNUMFMT "\n",
3804 infunc->name, infunc->table[i].location);
3805 return;
3806 }
3807 return;
3808 }
3809 if (infunc->forward.dimen){
3810 if (parseunit(&theunit, infunc->forward.dimen, 0, 0) ||
3811 completereduce(&theunit)){
3812 printf("Function '%s' has invalid units '%s'\n",
3813 infunc->name, infunc->forward.dimen);
3814 freeunit(&theunit);
3815 return;
3816 }
3817 } else initializeunit(&theunit);
3818 if (infunc->forward.domain_max && infunc->forward.domain_min)
3819 theunit.factor *=
3820 (*infunc->forward.domain_max+*infunc->forward.domain_min)/2;
3821 else if (infunc->forward.domain_max)
3822 theunit.factor = theunit.factor * *infunc->forward.domain_max - 1;
3823 else if (infunc->forward.domain_min)
3824 theunit.factor = theunit.factor * *infunc->forward.domain_min + 1;
3825 else
3826 theunit.factor *= 7; /* Arbitrary choice where we evaluate inverse */
3827 if (infunc->forward.dimen){
3828 unitcopy(&saveunit, &theunit);
3829 err = evalfunc(&theunit, infunc, FUNCTION, ALLERR);
3830 if (err) {
3831 printf("Error in definition %s(%s) as '%s':\n",
3832 infunc->name, infunc->forward.param, infunc->forward.def);
3833 printf(" %s\n",errormsg[err]);
3834 freeunit(&theunit);
3835 freeunit(&saveunit);
3836 return;
3837 }
3838 } else {
3839# define MAXPOWERTOCHECK 4
3840 struct unittype resultunit, arbunit;
3841 char unittext[9];
3842 double factor;
3843 int errors[MAXPOWERTOCHECK], errcount=0;
3844 char *indent;
3845
3846 strcpy(unittext,"(kg K)^ ");
3847 factor = theunit.factor;
3848 initializeunit(&saveunit);
3849 initializeunit(&resultunit);
3850 for(i=0;i<MAXPOWERTOCHECK;i++){
3851 lastchar(unittext) = '0'+i;
3852 err = parseunit(&arbunit, unittext, 0, 0);
3853 if (err) initializeunit(&arbunit);
3854 arbunit.factor = factor;
3855 unitcopy(&resultunit, &arbunit);
3856 errors[i] = evalfunc(&resultunit, infunc, FUNCTION, ALLERR);
3857 if (errors[i]) errcount++;
3858 else {
3859 freeunit(&saveunit);
3860 freeunit(&theunit);
3861 unitcopy(&saveunit, &arbunit);
3862 unitcopy(&theunit, &resultunit);
3863 }
3864 freeunit(&resultunit);
3865 freeunit(&arbunit);
3866 }
3867 if (!errors[0] && errcount==3) {
3868 printf("Warning: function '%s(%s)' defined as '%s'\n",
3869 infunc->name, infunc->forward.param, infunc->forward.def);
3870 printf(" appears to require a dimensionless argument, 'units' keyword not given\n");
3871 indent = " ";
3872 }
3873 else if (errcount==MAXPOWERTOCHECK) {
3874 printf("Error or missing 'units' keyword in definion %s(%s) as '%s'\n",
3875 infunc->name, infunc->forward.param, infunc->forward.def);
3876 indent=" ";
3877 }
3878 else if (errcount){
3879 printf("Warning: function '%s(%s)' defined as '%s'\n",
3880 infunc->name, infunc->forward.param, infunc->forward.def);
3881 printf(" failed for some test inputs:\n");
3882 indent = " ";
3883 }
3884 for(i=0;i<MAXPOWERTOCHECK;i++)
3885 if (errors[i]) {
3886 lastchar(unittext) = '0'+i;
3887 printf("%s%s(",indent,infunc->name);
3888 printf(num_format.format, factor);
3889 printf("%s): %s\n", unittext, errormsg[errors[i]]);
3890 }
3891 }
3892 if (completereduce(&theunit)){
3893 printf("Definition %s(%s) as '%s' is irreducible\n",
3894 infunc->name, infunc->forward.param, infunc->forward.def);
3895 freeunit(&theunit);
3896 freeunit(&saveunit);
3897 return;
3898 }
3899 if (!(infunc->inverse.def)){
3900 printf("Warning: no inverse for function '%s'\n", infunc->name);
3901 freeunit(&theunit);
3902 freeunit(&saveunit);
3903 return;
3904 }
3905 err = evalfunc(&theunit, infunc, INVERSE, ALLERR);
3906 if (err){
3907 printf("Error in inverse ~%s(%s) as '%s':\n",
3908 infunc->name,infunc->inverse.param, infunc->inverse.def);
3909 printf(" %s\n",errormsg[err]);
3910 freeunit(&theunit);
3911 freeunit(&saveunit);
3912 return;
3913 }
3914 divunit(&theunit, &saveunit);
3915 if (unit2num(&theunit) || fabs(theunit.factor-1)>1e-12)
3916 printf("Inverse is not the inverse for function '%s'\n", infunc->name);
3917 freeunit(&theunit);
3918}
3919
3920
3921struct namedef {
3922 char *name;
3923 char *def;
3924};
3925
3926#define CONFORMABLE 1
3927#define TEXTMATCH 2
3928
3929void
3930addtolist(struct unittype *have, char *searchstring, char *rname, char *name,
3931 char *def, struct namedef **list, int *listsize,
3932 int *maxnamelen, int *count, int searchtype)
3933{
3934 struct unittype want;
3935 int len = 0;
3936 int keepit = 0;
3937
3938 if (!name)
3939 return;
3940 if (searchtype==CONFORMABLE){
3941 initializeunit(&want);
3942 if (!parseunit(&want, name,0,0) && !completereduce(&want))
3943 keepit = !compareunits(have,&want,ignore_dimless);
3944 } else if (searchtype == TEXTMATCH) {
3945 keepit = (strstr(rname,searchstring) != NULL);
3946 }
3947 if (keepit){
3948 if (*count==*listsize){
3949 *listsize += 100;
3950 *list = (struct namedef *)
3951 realloc(*list,*listsize*sizeof(struct namedef));
3952 if (!*list){
3953 fprintf(stderr, "%s: memory allocation error (addtolist)\n",
3954 progname);
3955 exit(EXIT_FAILURE);
3956 }
3957 }
3958 (*list)[*count].name = rname;
3959 if (strchr(def, PRIMITIVECHAR))
3960 (*list)[*count].def = "<primitive unit>";
3961 else
3962 (*list)[*count].def = def;
3963 (*count)++;
3964 len = strwidth(name);
3965 if (len>*maxnamelen)
3966 *maxnamelen = len;
3967 }
3968 if (searchtype == CONFORMABLE)
3969 freeunit(&want);
3970}
3971
3972
3973int
3974compnd(const void *a, const void *b)
3975{
3976 return strcmp(((struct namedef *)a)->name, ((struct namedef *)b)->name);
3977}
3978
3979
3980int
3981screensize()
3982{
3983 int lines = 20; /* arbitrary but probably safe value */
3984
3985#if defined (_WIN32) && defined (_MSC_VER)
3986 CONSOLE_SCREEN_BUFFER_INFO csbi;
3987
3988 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
3989 lines = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
3990#endif
3991
3992#ifdef HAVE_IOCTL
3993 struct winsize ws;
3994 int fd;
3995 fd = open("/dev/tty", O_RDWR);
3996 if (fd>=0 && ioctl(fd, TIOCGWINSZ, &ws)==0)
3997 lines = ws.ws_row;
3998#endif
3999
4000 return lines;
4001}
4002
4003/*
4004 determines whether the output will fit on the screen, and opens a
4005 pager if it won't
4006*/
4007FILE *
4008get_output_fp(int lines)
4009{
4010 FILE *fp = NULL;
4011
4012 if (isatty(fileno(stdout)) && screensize() < lines) {
4013 if ((fp = popen(pager, "w")) == NULL) {
4014 fprintf(stderr, "%s: can't run pager '%s--'", progname, pager);
4015 perror((char*) NULL);
4016 }
4017 }
4018 if (!fp)
4019 fp = stdout;
4020
4021 return fp;
4022}
4023
4024int
4025countlines(char *msg)
4026{
4027 int nlines = 0;
4028 char *p;
4029
4030 for (p = msg; *p; p++)
4031 if (*p == '\n')
4032 nlines++;
4033
4034 return nlines;
4035}
4036
4037/*
4038 If have is non-NULL then search through all units and print the ones
4039 which are conformable with have. Otherwise search through all the
4040 units for ones whose names contain the second argument as a substring.
4041*/
4042
4043void
4044tryallunits(struct unittype *have, char *searchstring)
4045{
4046 struct unitlist *uptr;
4047 struct namedef *list;
4048 int listsize, maxnamelen, count;
4049 struct func *funcptr;
4050 struct wantalias *aliasptr;
4051 int i, j, searchtype;
4052 FILE *outfile;
4053 char *seploc, *firstunit;
4054
4055 list = (struct namedef *) mymalloc( 100 * sizeof(struct namedef),
4056 "(tryallunits)");
4057 listsize = 100;
4058 maxnamelen = 0;
4059 count = 0;
4060
4061 if (have)
4062 searchtype = CONFORMABLE;
4063 else {
4064 if (!searchstring)
4065 searchstring="";
4066 searchtype = TEXTMATCH;
4067 }
4068
4069 for(i=0;i<HASHSIZE;i++)
4070 for (uptr = utab[i]; uptr; uptr = uptr->next)
4071 addtolist(have, searchstring, uptr->name, uptr->name, uptr->value,
4072 &list, &listsize, &maxnamelen, &count, searchtype);
4073 for(i=0;i<SIMPLEHASHSIZE;i++)
4074 for(funcptr=ftab[i];funcptr;funcptr=funcptr->next){
4075 if (funcptr->table)
4076 addtolist(have, searchstring, funcptr->name, funcptr->tableunit,
4077 "<piecewise linear>", &list, &listsize, &maxnamelen, &count,
4078 searchtype);
4079 else
4080 addtolist(have, searchstring, funcptr->name, funcptr->inverse.dimen,
4081 "<nonlinear>", &list, &listsize, &maxnamelen, &count,
4082 searchtype);
4083 }
4084 for(aliasptr=firstalias;aliasptr;aliasptr=aliasptr->next){
4085 firstunit = dupstr(aliasptr->definition);/* coverity[var_assigned] */
4086 seploc = strchr(firstunit,UNITSEPCHAR); /* Alias definitions allowed in */
4087 *seploc = 0; /* database contain UNITSEPCHAR */
4088 addtolist(have, searchstring, aliasptr->name, firstunit,
4089 aliasptr->definition, &list, &listsize, &maxnamelen, &count,
4090 searchtype);
4091 free(firstunit);
4092 }
4093
4094 qsort(list, count, sizeof(struct namedef), compnd);
4095
4096 if (count==0)
4097 puts("No matching units found.");
4098#ifdef SIGPIPE
4099 signal(SIGPIPE, SIG_IGN);
4100#endif
4101 /* see if we need a pager */
4102 outfile = get_output_fp(count);
4103 for(i=0;i<count;i++){
4104 fputs(list[i].name,outfile);
4105 if (flags.verbose > 0 || flags.interactive) {
4106 for(j=strwidth(list[i].name);j<=maxnamelen;j++)
4107 putc(' ',outfile);
4108 tightprint(outfile,list[i].def);
4109 }
4110 fputc('\n',outfile);
4111 }
4112 if (outfile != stdout)
4113 pclose(outfile);
4114#ifdef SIGPIPE
4115 signal(SIGPIPE, SIG_DFL);
4116#endif
4117}
4118
4119
4120/* If quiet is false then prompt user with the query.
4121
4122 Fetch one line of input and return it in *buffer.
4123
4124 The bufsize argument is a dummy argument if we are using readline.
4125 The readline version frees buffer if it is non-null. The other
4126 version keeps the same buffer and grows it as needed.
4127
4128 If no data is read, then this function exits the program.
4129*/
4130
4131
4132void
4133getuser_noreadline(char **buffer, int *bufsize, const char *query)
4134{
4135#ifdef SUPPORT_UTF8
4136 int valid = 0;
4137 while(!valid){
4138 fputs(query, stdout);
4139 if (!fgetslong(buffer, bufsize, stdin,0)){
4140 if (!flags.quiet)
4141 putchar('\n');
4142 exit(EXIT_SUCCESS);
4143 }
4144 valid = strwidth(*buffer)>=0;
4145 if (!valid)
4146 printf("Error: %s\n",invalid_utf8);
4147 }
4148#else
4149 fputs(query, stdout);
4150 if (!fgetslong(buffer, bufsize, stdin,0)){
4151 if (!flags.quiet)
4152 putchar('\n');
4153 exit(EXIT_SUCCESS);
4154 }
4155#endif
4156}
4157
4158
4159#ifndef READLINE
4160# define getuser getuser_noreadline
4161#else
4162 /* we DO have readline */
4163void
4164getuser_readline(char **buffer, int *bufsize, const char *query)
4165{
4166#ifdef SUPPORT_UTF8
4167 int valid = 0;
4168 while (!valid){
4169 if (*buffer) free(*buffer);
4170 *buffer = readline(query);
4171 if (*buffer)
4172 replacectrlchars(*buffer);
4173 if (!*buffer || strwidth(*buffer)>=0)
4174 valid=1;
4175 else
4176 printf("Error: %s\n",invalid_utf8);
4177 }
4178#else
4179 if (*buffer) free(*buffer);
4180 *buffer = readline(query);
4181 if (*buffer)
4182 replacectrlchars(*buffer);
4183#endif
4184 if (nonempty(*buffer)) add_history(*buffer);
4185 if (!*buffer){
4186 if (!flags.quiet)
4187 putchar('\n');
4188 exit(EXIT_SUCCESS);
4189 }
4190}
4191
4192
4193void
4194getuser(char **buffer, int *bufsize, const char *query)
4195{
4196 if (flags.readline)
4197 getuser_readline(buffer,bufsize,query);
4198 else
4199 getuser_noreadline(buffer,bufsize,query);
4200}
4201
4202
4203/* Unit name completion for readline.
4204
4205 Complete function names or alias names or builtin functions.
4206
4207 Complete to the end of a prefix or complete to the end of a unit.
4208 If the text includes a full prefix plus part of a unit and if the
4209 prefix is longer than one character then complete that compound.
4210 Don't complete a prefix fragment into prefix plus anything.
4211*/
4212
4213#define CU_ALIAS 0
4214#define CU_BUILTIN 1
4215#define CU_FUNC 2
4216#define CU_PREFIX 3
4217#define CU_UNITS 4
4218#define CU_DONE 5
4219
4220char *
4221completeunits(char *text, int state)
4222{
4223 static int uhash, fhash, phash, checktype;
4224 static struct prefixlist *curprefix, *unitprefix;
4225 static struct unitlist *curunit;
4226 static struct func *curfunc;
4227 static struct wantalias *curalias;
4228 static char **curbuiltin;
4229 char *output = 0;
4230
4231#ifndef NO_SUPPRESS_APPEND
4232 rl_completion_suppress_append = 1;
4233#endif
4234
4235 if (!state){ /* state == 0 means this is the first call, so initialize */
4236 checktype = 0; /* start at first type */
4237 fhash = uhash = phash = 0;
4238 unitprefix=0; /* search for unit continuations starting with this prefix */
4239 curfunc=ftab[fhash];
4240 curunit=utab[uhash];
4241 curprefix=ptab[phash];
4242 curbuiltin = builtins;
4243 curalias = firstalias;
4244 }
4245 while (checktype != CU_DONE){
4246 if (checktype == CU_ALIAS){
4247 while(curalias){
4248 if (startswith(curalias->name,text))
4249 output = dupstr(curalias->name);
4250 curalias = curalias->next;
4251 if (output) return output;
4252 }
4253 checktype++;
4254 }
4255 if (checktype == CU_BUILTIN){
4256 while(*curbuiltin){
4257 if (startswith(*curbuiltin,text))
4258 output = dupstr(*curbuiltin);
4259 curbuiltin++;
4260 if (output) return output;
4261 }
4262 checktype++;
4263 }
4264 while (checktype == CU_FUNC){
4265 while (!curfunc && fhash<SIMPLEHASHSIZE-1){
4266 fhash++;
4267 curfunc = ftab[fhash];
4268 }
4269 if (!curfunc)
4270 checktype++;
4271 else {
4272 if (startswith(curfunc->name,text))
4273 output = dupstr(curfunc->name);
4274 curfunc = curfunc->next;
4275 if (output) return output;
4276 }
4277 }
4278 while (checktype == CU_PREFIX){
4279 while (!curprefix && phash<SIMPLEHASHSIZE-1){
4280 phash++;
4281 curprefix = ptab[phash];
4282 }
4283 if (!curprefix)
4284 checktype++;
4285 else {
4286 if (startswith(curprefix->name,text))
4287 output = dupstr(curprefix->name);
4288 curprefix = curprefix->next;
4289 if (output) return output;
4290 }
4291 }
4292 while (checktype == CU_UNITS){
4293 while (!curunit && uhash<HASHSIZE-1){
4294 uhash++;
4295 curunit = utab[uhash];
4296 }
4297 /* If we're done with the units go through them again with */
4298 /* the largest possible prefix stripped off */
4299 if (!curunit && !unitprefix
4300 && (unitprefix = plookup(text)) && strlen(unitprefix->name)>1){
4301 uhash = 0;
4302 curunit = utab[uhash];
4303 }
4304 if (!curunit) {
4305 checktype++;
4306 break;
4307 }
4308 if (unitprefix){
4309 if (startswith(curunit->name, text+unitprefix->len)){
4310 output = (char *)mymalloc(1+strlen(curunit->name)+unitprefix->len,
4311 "(completeunits)");
4312 strcpy(output, unitprefix->name);
4313 strcat(output, curunit->name);
4314 }
4315 }
4316 else if (startswith(curunit->name,text))
4317 output = dupstr(curunit->name);
4318 curunit=curunit->next;
4319 if (output)
4320 return output;
4321 }
4322 }
4323 return 0;
4324}
4325
4326#endif /* READLINE */
4327
4328
4329
4330/* see if current directory contains an executable file */
4331int
4332checkcwd (char *file)
4333{
4334 FILE *fp;
4335 char *p;
4336
4337 fp = openfile(file, "r");
4338 if (fp){
4339 fclose(fp);
4340 return 1;
4341 }
4342#ifdef _WIN32
4343 else if (!((p = strrchr(file, '.')) && isexe(p))) {
4344 char *pathname;
4345
4346 pathname = mymalloc(strlen(file) + strlen(EXE_EXT) + 1,
4347 "(checkcwd)");
4348 strcpy(pathname, file);
4349 strcat(pathname, EXE_EXT);
4350 fp = openfile(pathname, "r");
4351 free(pathname);
4352 if (fp) {
4353 fclose(fp);
4354 return 1;
4355 }
4356 }
4357#endif
4358
4359 return 0;
4360}
4361
4362/*
4363 return the last component of a pathname, and remove a .exe extension
4364 if one exists.
4365*/
4366
4367char *
4368getprogramname(char *path)
4369{
4370 size_t proglen;
4371 char *p;
4372
4373 path = pathend(path);
4374
4375 /* get rid of filename extensions in Windows */
4376 proglen = strlen(path);
4377
4378 if ((p = strrchr(path, '.')) && isexe(p))
4379 proglen -= 4;
4380 return dupnstr(path, proglen);
4381}
4382
4383/*
4384 Find the directory that contains the invoked executable.
4385*/
4386
4387char *
4388getprogdir(char *progname, char **fullprogname)
4389{
4390 char *progdir = NULL;
4391 char *p;
4392
4393#if defined (_WIN32) && defined (_MSC_VER)
4394 char buf[FILENAME_MAX + 1];
4395
4396 /* get the full pathname of the current executable and be done with it */
4397 /* TODO: is there way to do this with gcc? */
4398
4399 if (GetModuleFileName(NULL, buf, FILENAME_MAX + 1))
4400 progdir = dupstr(buf);
4401#endif
4402
4403 /* If path name is absolute or includes more than one component use it */
4404
4405 if (!progdir && (isfullpath(progname) || hasdirsep(progname)))
4406 progdir = dupstr(progname);
4407
4408
4409 /*
4410 command.com and cmd.exe under Windows always run a program that's in the
4411 current directory whether or not the current directory is in PATH, so we need
4412 to check the current directory.
4413
4414 This could return a false positive if units is run from a Unix-like command
4415 interpreter under Windows if the current directory is not in PATH but
4416 contains 'units' or 'units.exe'
4417 */
4418#if defined (_WIN32) && !defined (_MSC_VER)
4419 if (!progdir && checkcwd(progname))
4420 progdir = dupstr(progname);
4421#endif
4422
4423 /* search PATH to find the executable */
4424 if (!progdir) {
4425 char *env;
4426 env = getenv("PATH");
4427 if (env) {
4428 /* search PATH */
4429 char *direc, *direc_end, *pathname;
4430 int len;
4431 FILE *fp;
4432
4433 pathname = mymalloc(strlen(env)+strlen(progname)+strlen(EXE_EXT)+2,
4434 "(getprogdir)");
4435 direc = env;
4436 while (direc) {
4437 direc_end = strchr(direc,PATHSEP);
4438 if (!direc_end)
4439 len = strlen(direc);
4440 else
4441 len = direc_end-direc;
4442 strncpy(pathname, direc, len);
4443 if (len>0)
4444 pathname[len++]=DIRSEP;
4445 strcpy(pathname+len, progname);
4446 fp = openfile(pathname, "r");
4447 if (fp){
4448 progdir = dupstr(pathname);
4449 break;
4450 }
4451#ifdef _WIN32
4452 /*
4453 executable may or may not have '.exe' suffix, so we need to
4454 look for both
4455 */
4456 if (!((p = strrchr(pathname, '.')) && isexe(p))) {
4457 strcat(pathname, EXE_EXT);
4458 fp = openfile(pathname, "r");
4459 if (fp){
4460 progdir = dupstr(pathname);
4461 break;
4462 }
4463 }
4464#endif
4465 direc = direc_end;
4466 if (direc) direc++;
4467 }
4468 free(pathname);
4469 if (fp)
4470 fclose(fp);
4471 }
4472 }
4473
4474 if (!progdir) {
4475 fprintf(stderr, "%s: cannot find program directory\n", progname);
4476 exit(EXIT_FAILURE);
4477 }
4478
4479 *fullprogname = dupstr(progdir); /* used by printversion() */
4480 p = pathend(progdir);
4481 *p = '\0';
4482
4483 return progdir;
4484}
4485
4486/*
4487 find a possible data directory relative to a 'bin' directory that
4488 contains the executable
4489*/
4490
4491char *
4492getdatadir()
4493{
4494 int progdirlen;
4495 char *p;
4496
4497 if (isfullpath(DATADIR))
4498 return DATADIR;
4499 if (!progdir || emptystr(DATADIR))
4500 return NULL;
4501 progdirlen = strlen(progdir);
4502 datadir = (char *) mymalloc(progdirlen + strlen(DATADIR) + 2,
4503 "(getdatadir)");
4504 strcpy(datadir, progdir);
4505 if (isdirsep(progdir[progdirlen - 1]))
4506 datadir[progdirlen - 1] = '\0'; /* so pathend() can work */
4507 p = pathend(datadir);
4508 if ((strlen(p) == 3) && (tolower(p[0]) == 'b') \
4509 && (tolower(p[1]) == 'i') && (tolower(p[2]) == 'n')) {
4510 p = DATADIR;
4511 while (*p == '.') /* ignore "./", "../" */
4512 p++;
4513 if (isdirsep(*p))
4514 p++;
4515 strcpy(pathend(datadir), p);
4516
4517 return datadir;
4518 }
4519 else
4520 return NULL;
4521}
4522
4523void
4524showfilecheck(int errnum, char *filename)
4525{
4526 if (errnum==ENOENT)
4527 printf(" Checking %s\n", filename);
4528 else
4529 printf(" Checking %s: %s\n", filename, strerror(errnum));
4530}
4531
4532/*
4533 On Windows, find the locale map
4534
4535 If print is set to ERRMSG then display error message if the file is
4536 not valid. If print is set to SHOWFILES then display files that are
4537 checked (when the filename is not a fully specified path). If print
4538 is set to NOERRMSG then do not display anything.
4539
4540 Returns filename of valid local map file or NULL if no file was found.
4541*/
4542#ifdef _WIN32
4543char *
4544findlocalemap(int print)
4545{
4546 FILE *map = NULL;
4547 char *filename = NULL;
4548 char *file;
4549
4550 /*
4551 Try the environment variable UNITSLOCALEMAP, then the #defined
4552 value LOCALEMAP, then the directory containing the units
4553 executable, then the directory given by datadir, and finally
4554 the directory containing the units data file.
4555 */
4556
4557 /* check the environment variable UNITSLOCALEMAP */
4558 file = getenv("UNITSLOCALEMAP");
4559 if (nonempty(file)) {
4560 map = openfile(file,"rt");
4561 if (!map) {
4562 if (print == ERRMSG) {
4563 fprintf(stderr,
4564 "%s: cannot open locale map '%s'\n specified in UNITSLOCALEMAP environment variable. ",
4565 progname, file);
4566 perror((char *) NULL);
4567 }
4568 return NULL;
4569 }
4570 else
4571 filename = dupstr(file);
4572 }
4573
4574 /* check the #defined value LOCALEMAP */
4575 if (!map) {
4576 file = LOCALEMAP;
4577 map = openfile(file,"rt");
4578 if (map)
4579 filename = dupstr(file);
4580 }
4581
4582 if (!map && !progdir) {
4583 if (print == ERRMSG) {
4584 fprintf(stderr,
4585 "%s: cannot find locale map--program directory not set\n",
4586 progname);
4587 exit(EXIT_FAILURE);
4588 }
4589 else
4590 return NULL;
4591 }
4592
4593 /* check the directory with the units executable */
4594 if (!map) {
4595 filename = (char *) mymalloc(strlen(progdir) + strlen(file) + 2,
4596 "(findlocalemap)");
4597 strcpy(filename, progdir);
4598 strcat(filename, file);
4599 map = openfile(filename,"rt");
4600 if (print==SHOWFILES && !map)
4601 showfilecheck(errno, filename);
4602 }
4603
4604 /* check data directory */
4605 if (!map && datadir) {
4606 if (filename)
4607 free(filename);
4608 filename = (char *) mymalloc(strlen(datadir) + strlen(file) + 3,
4609 "(findlocalemap)");
4610 strcpy(filename, datadir);
4611 strcat(filename, DIRSEPSTR);
4612 strcat(filename, file);
4613 map = openfile(filename, "rt");
4614 if (print==SHOWFILES && !map)
4615 showfilecheck(errno, filename);
4616 }
4617
4618 /* check the directory with the units data file */
4619 if (!map && unitsfiles[0]) {
4620 char *lastfilename = NULL;
4621
4622 if (filename) {
4623 if (datadir)
4624 lastfilename = dupstr(filename);
4625 free(filename);
4626 }
4627 filename = (char *) mymalloc(strlen(unitsfiles[0]) + strlen(file) + 2,
4628 "(findlocalemap)");
4629 strcpy(filename, unitsfiles[0]);
4630 strcpy(pathend(filename), file);
4631
4632 /* don't bother if we've just checked for it */
4633 if (lastfilename && strcmp(filename, lastfilename)) {
4634 map = openfile(filename,"rt");
4635 if (print==SHOWFILES && !map)
4636 showfilecheck(errno, filename);
4637 }
4638 }
4639
4640 if (map) {
4641 fclose(map);
4642 return filename;
4643 }
4644 else {
4645 if (filename)
4646 free(filename);
4647 return NULL;
4648 }
4649}
4650#endif
4651
4652/*
4653 Find the units database file.
4654
4655 If print is set to ERRMSG then display error message if the file is
4656 not valid. If print is set to SHOWFILES then display files that are
4657 checked (when the filename is not a fully specified path). If print
4658 is set to NOERRMSG then do not display anything.
4659
4660 Returns filename of valid database file or NULL if no file
4661 was found.
4662*/
4663
4664char *
4665findunitsfile(int print)
4666{
4667 FILE *testfile=0;
4668 char *file;
4669
4670 file = getenv("UNITSFILE");
4671 if (nonempty(file)) {
4672 testfile = openfile(file, "rt");
4673 if (!testfile) {
4674 if (print==ERRMSG) {
4675 fprintf(stderr,
4676 "%s: cannot open units file '%s' in environment variable UNITSFILE. ",
4677 progname, file);
4678 perror((char *) NULL);
4679 }
4680 return NULL;
4681 }
4682 }
4683
4684 if (!testfile && isfullpath(UNITSFILE)){
4685 file = UNITSFILE;
4686 testfile = openfile(file, "rt");
4687 if (!testfile) {
4688 if (print==ERRMSG) {
4689 fprintf(stderr,
4690 "%s: cannot open units data file '%s'. ", progname, UNITSFILE);
4691 perror((char *) NULL);
4692 }
4693 return NULL;
4694 }
4695 }
4696
4697 if (!testfile && !progdir) {
4698 if (print==ERRMSG) {
4699 fprintf(stderr,
4700 "%s: cannot open units file '%s' and cannot find program directory.\n", progname, UNITSFILE);
4701 perror((char *) NULL);
4702 }
4703 return NULL;
4704 }
4705
4706 if (!testfile) {
4707 /* check the directory containing the units executable */
4708 file = (char *) mymalloc(strlen(progdir)+strlen(UNITSFILE)+1,
4709 "(findunitsfile)");
4710 strcpy(file, progdir);
4711 strcat(file, UNITSFILE);
4712 testfile = openfile(file, "rt");
4713 if (print==SHOWFILES && !testfile)
4714 showfilecheck(errno, file);
4715 if (!testfile)
4716 free(file);
4717 }
4718
4719 /* check data directory */
4720 if (!testfile && datadir) {
4721 file = (char *) mymalloc(strlen(datadir) + strlen(UNITSFILE) + 2,
4722 "(findunitsfile)");
4723 strcpy(file, datadir);
4724 strcat(file, DIRSEPSTR);
4725 strcat(file, UNITSFILE);
4726 testfile = openfile(file, "rt");
4727 if (print==SHOWFILES && !testfile)
4728 showfilecheck(errno, file);
4729 if (!testfile)
4730 free(file);
4731 }
4732
4733 if (!testfile) {
4734 if (print==ERRMSG)
4735 fprintf(stderr,"%s: cannot find units file '%s'\n", progname, UNITSFILE);
4736 return NULL;
4737 }
4738 else {
4739 fclose(testfile);
4740 return file;
4741 }
4742}
4743
4744/*
4745 Find the user's home directory. Unlike *nix, Windows doesn't usually
4746 set HOME, so we check several other places as well.
4747*/
4748char *
4749findhome(char **errmsg)
4750{
4751 struct stat statbuf;
4752 int allocated = 0;
4753 char *homedir;
4754 char notfound[] = "Specified home directory '%s' does not exist";
4755 char notadir[] = "Specified home directory '%s' is not a directory";
4756
4757 /*
4758 Under UNIX just check HOME. Under Windows, if HOME is not set then
4759 check HOMEDRIVE:HOMEPATH and finally USERPROFILE
4760 */
4761 homedir = getenv("HOME");
4762
4763#ifdef _WIN32
4764 if (!nonempty(homedir)) {
4765 /* try a few other places */
4766 /* try HOMEDRIVE and HOMEPATH */
4767 char *homedrive, *homepath;
4768 if ((homedrive = getenv("HOMEDRIVE")) && *homedrive && (homepath = getenv("HOMEPATH")) && *homepath) {
4769 homedir = mymalloc(strlen(homedrive)+strlen(homepath)+1,"(personalfile)");
4770 allocated = 1;
4771 strcpy(homedir, homedrive);
4772 strcat(homedir, homepath);
4773 }
4774 else
4775 /* finally, try USERPROFILE */
4776 homedir = getenv("USERPROFILE");
4777 }
4778#endif
4779
4780 /*
4781 If a home directory is specified, see if it exists and is a
4782 directory. If not set error message text.
4783 */
4784
4785 if (nonempty(homedir)) {
4786 if (stat(homedir, &statbuf) != 0) {
4787 *errmsg = malloc(strlen(notfound)+strlen(homedir));
4788 sprintf(*errmsg, notfound, homedir);
4789 }
4790 else if (!(statbuf.st_mode & S_IFDIR)) {
4791 *errmsg = malloc(strlen(notadir)+strlen(homedir));
4792 sprintf(*errmsg, notadir, homedir);
4793 }
4794 if (!allocated)
4795 homedir = dupstr(homedir);
4796 return homedir;
4797 }
4798 else {
4799 *errmsg = "no home directory";
4800 return NULL;
4801 }
4802}
4803
4804/*
4805 Find a personal file. First checks the specified environment variable
4806 (envname) for the filename to use. If this is unset then search user's
4807 home directory for basename. If there is no home directory, returns
4808 NULL. Otherwise if the file exists then returns its name in newly allocated
4809 space and sets *exists to 1. If the file does not exist then sets *exist to
4810 zero and:
4811 With checkonly == 0, prints error message and returns NULL
4812 With checkonly != 0, returns filename (does not print error message)
4813*/
4814char *
4815personalfile(const char *envname, const char *basename,
4816 int checkonly, int *exists)
4817{
4818 FILE *testfile;
4819 char *filename=NULL;
4820
4821 *exists = 0;
4822
4823 /* First check the specified environment variable for a file name */
4824 if (envname)
4825 filename = getenv(envname);
4826 if (nonempty(filename)){
4827 testfile = openfile(filename, "rt");
4828 if (testfile){
4829 fclose(testfile);
4830 *exists = 1;
4831 return filename;
4832 }
4833 if (checkonly)
4834 return filename;
4835 else {
4836 fprintf(stderr, "%s: cannot open file '%s' specified in %s environment variable: ",
4837 progname, filename, envname);
4838 perror((char *) NULL);
4839 return NULL;
4840 }
4841 }
4842 /* Environment variable not set: look in home directory */
4843 else if (nonempty(homedir)) {
4844 filename = mymalloc(strlen(homedir)+strlen(basename)+2,
4845 "(personalfile)");
4846 strcpy(filename,homedir);
4847
4848 if (strcmp(homedir, "/") && strcmp(homedir, "\\"))
4849 strcat(filename,DIRSEPSTR);
4850 strcat(filename,basename);
4851
4852 testfile = openfile(filename, "rt");
4853 if (testfile){
4854 fclose(testfile);
4855 *exists = 1;
4856 return filename;
4857 }
4858 if (checkonly)
4859 return filename;
4860 else {
4861 if (errno==EACCES || errno==EISDIR) {
4862 fprintf(stderr,"%s: cannot open file '%s': ",progname,filename);
4863 perror(NULL);
4864 }
4865 free(filename);
4866 return NULL;
4867 }
4868 }
4869 else
4870 return NULL;
4871}
4872
4873
4874/* print usage message */
4875
4876void
4877usage()
4878{
4879 int nlines;
4880 char *unitsfile;
4881 char *msg = "\nUsage: %s [options] ['from-unit' 'to-unit']\n\n\
4882Options:\n\
4883 -h, --help show this help and exit\n\
4884 -c, --check check that all units reduce to primitive units\n\
4885 --check-verbose like --check, but lists units as they are checked\n\
4886 --verbose-check so you can find units that cause endless loops\n\
4887 -d, --digits show output to specified number of digits (default: %d)\n\
4888 -e, --exponential exponential format output\n\
4889 -f, --file specify a units data file (-f '' loads default file)\n"
4890#ifdef READLINE
4891"\
4892 -H, --history specify readline history file (-H '' disables history)\n"
4893#endif
4894"\
4895 -L, --log specify a file to log conversions\n\
4896 -l, --locale specify a desired locale\n\
4897 -m, --minus make - into a subtraction operator (default)\n\
4898 --oldstar use old '*' precedence, higher than '/'\n\
4899 --newstar use new '*' precedence, equal to '/'\n\
4900 -n, --nolists disable conversion to unit lists\n\
4901 -S, --show-factor show non-unity factor before 1|x in multi-unit output\n\
4902 --conformable in non-interactive mode, show all conformable units\n\
4903 -o, --output-format specify printf numeric output format (default: %%.%d%c)\n\
4904 -p, --product make '-' into a product operator\n\
4905 -q, --quiet suppress prompting\n\
4906 --silent same as --quiet\n\
4907 -s, --strict suppress reciprocal unit conversion (e.g. Hz<->s)\n\
4908 -v, --verbose show slightly more verbose output\n\
4909 --compact suppress printing of tab, '*', and '/' character\n\
4910 -1, --one-line suppress the second line of output\n\
4911 -t, --terse terse output (--strict --compact --quiet --one-line)\n\
4912 -r, --round round last element of unit list output to an integer\n\
4913 -U, --unitsfile show units data filename and exit\n\
4914 -u, --units specify a CGS units system or natural units system:\n\
4915 gauss[ian],esu,emu,hlu,natural,natural-gauss,\n\
4916 hartree,planck,planck-red,si\n\
4917 -V, --version show version, data filenames (with -t: version only)\n\
4918 -I, --info show version, files, and program properties\n";
4919 FILE *fp = NULL;
4920
4921 unitsfile = findunitsfile(NOERRMSG);
4922
4923 nlines = countlines(msg);
4924 /* see if we need a pager */
4925 fp = get_output_fp(nlines + 4);
4926
4927 fprintf(fp, msg, progname, DEFAULTPRECISION, DEFAULTPRECISION, DEFAULTTYPE);
4928 if (!unitsfile)
4929 fprintf(fp, "Units data file '%s' not found.\n\n", UNITSFILE);
4930 else
4931 fprintf(fp, "\nTo learn about the available units look in '%s'\n\n", unitsfile);
4932 fputs("Report bugs to adrianm@gnu.org.\n\n", fp);
4933
4934 if (fp != stdout)
4935 pclose(fp);
4936}
4937
4938/* Print message about how to get help */
4939
4940void
4941helpmsg()
4942{
4943 fprintf(stderr,"\nTry '%s --help' for more information.\n",progname);
4944 exit(EXIT_FAILURE);
4945}
4946
4947/* show units version, and optionally, additional information */
4948void
4949printversion()
4950{
4951 int exists;
4952 char *u_unitsfile = NULL; /* units data file specified in UNITSFILE */
4953 char *m_unitsfile; /* personal units data file from HOME_UNITS_ENV */
4954 char *p_unitsfile; /* personal units data file */
4955 FILE *fp, *histfile;
4956#ifdef _WIN32
4957 char *localemap;
4958#endif
4959
4960 if (flags.verbose == 0) {
4961 printf("GNU Units version %s\n", VERSION);
4962 return;
4963 }
4964
4965 printf("GNU Units version %s\n%s, %s, locale %s\n",
4966 VERSION, RVERSTR,UTF8VERSTR,mylocale);
4967#if defined (_WIN32) && defined (HAVE_MKS_TOOLKIT)
4968 puts("With MKS Toolkit");
4969#endif
4970
4971 if (flags.verbose == 2) {
4972 if (!fullprogname)
4973 getprogdir(progname, &fullprogname);
4974 if (fullprogname)
4975 printf("\n%s program is %s\n", progname, fullprogname);
4976 }
4977
4978 /* units data file */
4979
4980 putchar('\n');
4981 if (isfullpath(UNITSFILE))
4982 printf("Default units data file is '%s'\n", UNITSFILE);
4983 else
4984 printf("Default units data file is '%s';\n %s will search for this file\n",
4985 UNITSFILE, progname);
4986 if (flags.verbose < 2)
4987 printf("Default personal units file: %s\n", homeunitsfile);
4988
4989 if (flags.verbose == 2){
4990 u_unitsfile = getenv("UNITSFILE");
4991 if (u_unitsfile)
4992 printf("Environment variable UNITSFILE set to '%s'\n", u_unitsfile);
4993 else
4994 puts("Environment variable UNITSFILE not set");
4995
4996 unitsfiles[0] = findunitsfile(SHOWFILES);
4997
4998 if (unitsfiles[0]) {
4999 /* We searched for the file in program and data dirs */
5000 if (!isfullpath(UNITSFILE) && !nonempty(u_unitsfile))
5001 printf("Found data file '%s'\n", unitsfiles[0]);
5002 else
5003 printf("Units data file is '%s'\n", unitsfiles[0]);
5004 }
5005 else {
5006 if (errno && (nonempty(u_unitsfile) || isfullpath(UNITSFILE)))
5007 printf("*** Units data file invalid: %s ***\n",strerror(errno));
5008 else
5009 puts("*** Units data file not found ***");
5010 }
5011 if (homedir_error)
5012 printf("\n%s\n", homedir_error);
5013 else
5014 printf("\nHome directory is '%s'\n", homedir);
5015 }
5016
5017 /* personal units data file: environment */
5018 if (flags.verbose == 2){
5019 m_unitsfile = getenv(HOME_UNITS_ENV);
5020 putchar('\n');
5021 if (m_unitsfile) {
5022 printf("Environment variable %s set to '%s'\n",
5023 HOME_UNITS_ENV,m_unitsfile);
5024 }
5025 else
5026 printf("Environment variable %s not set\n", HOME_UNITS_ENV);
5027
5028 p_unitsfile = personalfile(HOME_UNITS_ENV, homeunitsfile, 1, &exists);
5029 if (p_unitsfile) {
5030 printf("Personal units data file is '%s'\n", p_unitsfile);
5031 if (!exists){
5032 if (homedir_error && !nonempty(m_unitsfile))
5033 printf(" (File invalid: %s)\n", homedir_error);
5034 else if (errno==ENOENT && !nonempty(m_unitsfile))
5035 puts(" (File does not exist)");
5036 else
5037 printf(" (File invalid: %s)\n",strerror(errno));
5038 }
5039 }
5040 else
5041 puts("Personal units data file not found: no home directory");
5042 }
5043#ifdef READLINE
5044 if (flags.verbose == 2) {
5045 historyfile = personalfile(NULL,HISTORY_FILE,1,&exists);
5046 if (historyfile){
5047 printf("\nDefault readline history file is '%s'\n", historyfile);
5048 histfile = openfile(historyfile,"r+");
5049 if (!histfile)
5050 printf(" (File invalid: %s)\n",
5051 homedir_error ? homedir_error : strerror(errno));
5052 else
5053 fclose(histfile);
5054 }
5055 else
5056 puts("\nReadline history file unusable: no home directory");
5057 }
5058#endif
5059
5060#ifdef _WIN32
5061 /* locale map */
5062 if (flags.verbose == 2) {
5063 putchar('\n');
5064 localemap = getenv("UNITSLOCALEMAP");
5065 if (localemap)
5066 printf("Environment variable UNITSLOCALEMAP set to '%s'\n", localemap);
5067 else
5068 puts("Environment variable UNITSLOCALEMAP not set");
5069
5070 if (isfullpath(LOCALEMAP))
5071 printf("Default locale map is '%s'\n", LOCALEMAP);
5072 else
5073 printf("Default locale map is '%s';\n %s will search for this file\n",
5074 LOCALEMAP, progname);
5075
5076 localemap = findlocalemap(SHOWFILES);
5077 if (localemap && !isfullpath(LOCALEMAP))
5078 printf("Found locale map '%s'\n", localemap);
5079 else if (localemap)
5080 printf("Locale map is '%s'\n", localemap);
5081 else
5082 puts("*** Locale map not found ***");
5083 }
5084#endif
5085
5086 printf("\n%s\n\n", LICENSE);
5087}
5088
5089void
5090showunitsfile()
5091{
5092 char *unitsfile;
5093 unitsfile = findunitsfile(NOERRMSG);
5094 if (unitsfile)
5095 printf("%s\n", unitsfile);
5096 else
5097 puts("Units data file not found");
5098}
5099
5100
5101char *shortoptions = "VIUu:vqechSstf:o:d:mnpr1l:L:"
5102#ifdef READLINE
5103 "H:"
5104#endif
5105 ;
5106
5107struct option longoptions[] = {
5108 {"check", no_argument, &flags.unitcheck, 1},
5109 {"check-verbose", no_argument, &flags.unitcheck, 2},
5110 {"compact", no_argument, &flags.verbose, 0},
5111 {"digits", required_argument, 0, 'd'},
5112 {"exponential", no_argument, 0, 'e'},
5113 {"file", required_argument, 0, 'f'},
5114 {"help", no_argument, 0, 'h'},
5115#ifdef READLINE
5116 {"history", required_argument, 0, 'H'},
5117#endif
5118 {"info", no_argument, 0, 'I'},
5119 {"locale", required_argument, 0, 'l'},
5120 {"log", required_argument, 0, 'L'},
5121 {"minus", no_argument, &parserflags.minusminus, 1},
5122 {"newstar", no_argument, &parserflags.oldstar, 0},
5123 {"nolists", no_argument, 0, 'n'},
5124 {"oldstar", no_argument, &parserflags.oldstar, 1},
5125 {"one-line", no_argument, &flags.oneline, 1},
5126 {"output-format", required_argument, 0, 'o'},
5127 {"product", no_argument, &parserflags.minusminus, 0},
5128 {"quiet", no_argument, &flags.quiet, 1},
5129 {"round",no_argument, 0, 'r'},
5130 {"show-factor", no_argument, 0, 'S'},
5131 {"conformable", no_argument, &flags.showconformable, 1 },
5132 {"silent", no_argument, &flags.quiet, 1},
5133 {"strict",no_argument,&flags.strictconvert, 1},
5134 {"terse",no_argument, 0, 't'},
5135 {"unitsfile", no_argument, 0, 'U'},
5136 {"units", required_argument, 0, 'u'},
5137 {"verbose", no_argument, &flags.verbose, 2},
5138 {"verbose-check", no_argument, &flags.unitcheck, 2},
5139 {"version", no_argument, 0, 'V'},
5140 {0,0,0,0} };
5141
5142/* Process the args. Returns 1 if interactive mode is desired, and 0
5143 for command line operation. If units appear on the command line
5144 they are returned in the from and to parameters. */
5145
5146int
5147processargs(int argc, char **argv, char **from, char **to)
5148{
5149 optarg = 0;
5150 optind = 0;
5151 int optchar, optindex;
5152 int ind;
5153 int doprintversion=0;
5154 char *unitsys=0, *temp;
5155
5156 while ( -1 !=
5157 (optchar =
5158 getopt_long(argc, argv,shortoptions,longoptions, &optindex ))) {
5159 switch (optchar) {
5160 case 'm':
5161 parserflags.minusminus = 1;
5162 break;
5163 case 'p':
5164 parserflags.minusminus = 0;
5165 break;
5166 case 't':
5167 flags.oneline = 1;
5168 flags.quiet = 1;
5169 flags.strictconvert = 1;
5170 flags.verbose = 0;
5171 break;
5172
5173 /* numeric output format */
5174 case 'd':
5175 if (checksigdigits(optarg) < 0)
5176 exit(EXIT_FAILURE);
5177 else /* ignore anything given with 'o' option */
5178 num_format.format = NULL;
5179 break;
5180 case 'e': /* ignore anything given with 'o' option */
5181 num_format.format = NULL;
5182 num_format.type = 'e';
5183 break;
5184 case 'o':
5185 num_format.format = optarg;
5186 break;
5187
5188 case 'c':
5189 flags.unitcheck = 1;
5190 break;
5191 case 'f':
5192 for(ind=0;unitsfiles[ind];ind++);
5193 if (ind==MAXFILES){
5194 fprintf(stderr, "At most %d -f specifications are allowed\n",
5195 MAXFILES);
5196 exit(EXIT_FAILURE);
5197 }
5198 if (optarg && *optarg)
5199 unitsfiles[ind] = optarg;
5200 else {
5201 unitsfiles[ind] = findunitsfile(ERRMSG);
5202 if (!unitsfiles[ind])
5203 exit(EXIT_FAILURE);
5204 }
5205 unitsfiles[ind+1] = 0;
5206 break;
5207 case 'L':
5208 logfilename = optarg;
5209 break;
5210 case 'l':
5211 mylocale = optarg;
5212 break;
5213 case 'n':
5214 flags.unitlists = 0;
5215 break;
5216 case 'q':
5217 flags.quiet = 1;
5218 break;
5219 case 'r':
5220 flags.round = 1;
5221 break;
5222 case 'S':
5223 flags.showfactor = 1;
5224 break;
5225 case 's':
5226 flags.strictconvert = 1;
5227 break;
5228 case 'v':
5229 flags.verbose = 2;
5230 break;
5231 case '1':
5232 flags.oneline = 1;
5233 break;
5234 case 'I':
5235 flags.verbose = 2; /* fall through */
5236 case 'V':
5237 doprintversion = 1;
5238 break;
5239 case 'U':
5240 showunitsfile();
5241 exit(EXIT_SUCCESS);
5242 break;
5243 case 'u':
5244 unitsys = optarg;
5245 for(ind=0;unitsys[ind];ind++)
5246 unitsys[ind] = tolower(unitsys[ind]);
5247 break;
5248 case 'h':
5249 usage();
5250 exit(EXIT_SUCCESS);
5251#ifdef READLINE
5252 case 'H':
5253 if (emptystr(optarg))
5254 historyfile=NULL;
5255 else
5256 historyfile = optarg;
5257 break;
5258#endif
5259 case 0: break; /* This is reached if a long option is
5260 processed with no return value set. */
5261 case '?': /* Invalid option or missing argument returns '?' */
5262 default:
5263 helpmsg(); /* helpmsg() exits with error */
5264 }
5265 }
5266
5267 temp = strchr(mylocale,'.');
5268 if (temp)
5269 *temp = '\0';
5270
5271 if (doprintversion){
5272 printversion();
5273 exit(EXIT_SUCCESS);
5274 }
5275
5276 /* command-line option overwrites environment */
5277 if (unitsys)
5278 setenv("UNITS_SYSTEM", unitsys, 1);
5279
5280 if (flags.unitcheck) {
5281 if (optind != argc){
5282 fprintf(stderr,
5283 "Too many arguments (arguments are not allowed with -c).\n");
5284 helpmsg(); /* helpmsg() exits with error */
5285 }
5286 } else {
5287 if (optind == argc - 2) {
5288 if (flags.showconformable) {
5289 fprintf(stderr,"Too many arguments (only one unit expression allowed with '--conformable').\n");
5290 helpmsg(); /* helpmsg() exits with error */
5291 }
5292 flags.quiet=1;
5293 *from = argv[optind];
5294 *to = dupstr(argv[optind+1]); /* This string may get rewritten later */
5295 return 0; /* and we might call free() on it */
5296 }
5297
5298 if (optind == argc - 1) {
5299 flags.quiet=1;
5300 *from = argv[optind];
5301 *to=0;
5302 return 0;
5303 }
5304 if (optind < argc - 2) {
5305 fprintf(stderr,"Too many arguments (maybe you need quotes).\n");
5306 helpmsg(); /* helpmsg() exits with error */
5307 }
5308 }
5309
5310 return 1;
5311}
5312
5313/*
5314 Show a pointer under the input to indicate a problem.
5315 Prints 'position' spaces and then the pointer.
5316 If 'position' is negative, nothing is printed.
5317 */
5318
5319void
5320showpointer(int position)
5321{
5322 if (position >= 0){
5323 while (position--) putchar(' ');
5324 puts(POINTER);
5325 }
5326} /* end showpointer */
5327
5328
5329/*
5330 Process the string 'unitstr' as a unit, placing the processed data
5331 in the unit structure 'theunit'. Returns 0 on success and 1 on
5332 failure. If an error occurs an error message is printed to stdout.
5333 A pointer ('^') will be printed if an error is detected, and promptlen
5334 should be set to the printing width of the prompt string, or set
5335 it to NOPOINT to supress printing of the pointer.
5336 */
5337
5338
5339int
5340processunit(struct unittype *theunit, char *unitstr, int promptlen)
5341{
5342 char *errmsg;
5343 int errloc,err;
5344 char savechar;
5345
5346 if (flags.unitlists && strchr(unitstr, UNITSEPCHAR)){
5347 puts("Unit list not allowed");
5348 return 1;
5349 }
5350 if ((err=parseunit(theunit, unitstr, &errmsg, &errloc))){
5351 if (promptlen >= 0){
5352 if (err!=E_UNKNOWNUNIT || !irreducible){
5353 if (errloc>0) {
5354 savechar = unitstr[errloc];
5355 unitstr[errloc] = 0;
5356 showpointer(promptlen+strwidth(unitstr)-1);
5357 unitstr[errloc] = savechar;
5358 }
5359 else showpointer(promptlen);
5360 }
5361 }
5362 else
5363 printf("Error in '%s': ", unitstr);
5364 fputs(errmsg,stdout);
5365 if (err==E_UNKNOWNUNIT && irreducible)
5366 printf(" '%s'", irreducible);
5367 putchar('\n');
5368 return 1;
5369 }
5370 if ((err=completereduce(theunit))){
5371 fputs(errormsg[err],stdout);
5372 if (err==E_UNKNOWNUNIT)
5373 printf(" '%s'", irreducible);
5374 putchar('\n');
5375 return 1;
5376 }
5377 return 0;
5378}
5379
5380
5381/* Checks for a new unit defined on the prompt with the form _<NAME> = <DEF> */
5382
5383int
5384definevariable(char *def, int promptlen)
5385{
5386 int dummy, err;
5387 struct unittype unit;
5388
5389 char *value = strchr(def,'=');
5390 if (!value)
5391 return 0;
5392 *value++=0;
5393 if (processunit(&unit, value, promptlen + (value-def)))
5394 return 1;
5395 removespaces(def);
5396 removespaces(value);
5397 err = *def!='_' || newunit(def,value, &dummy, 0, 0, NULL, 1, 1);
5398 if (err)
5399 printf("Invalid variable name: %s\n", def);
5400 return 1;
5401}
5402
5403
5404
5405
5406/*
5407 Checks the input parameter unitstr (a list of units separated by
5408 UNITSEPCHAR) for errors. All units must be parseable and
5409 conformable to each other. Returns 0 on success and 1 on failure.
5410
5411 If an error is found then print an error message on stdout. A
5412 pointer ('^') will be printed to mark the error. The promptlen
5413 parameter should be set to the printing width of the prompt string
5414 so that the pointer is correctly aligned.
5415
5416 To suppress the printing of the pointer set promptlen to NOPOINT.
5417 To suppress printing of error messages entirely set promptlen to
5418 NOERRMSG.
5419*/
5420
5421
5422int
5423checkunitlist(char *unitstr, int promptlen)
5424{
5425 struct unittype unit[2], one;
5426 char *firstunitstr,*nextunitstr;
5427 int unitidx = 0;
5428
5429 int printerror = promptlen != NOERRMSG;
5430
5431 initializeunit(&one);
5432
5433 firstunitstr = unitstr;
5434
5435 initializeunit(unit);
5436 initializeunit(unit+1);
5437
5438 while (unitstr) {
5439 if ((nextunitstr = strchr(unitstr, UNITSEPCHAR)) != 0)
5440 *nextunitstr = '\0';
5441
5442 if (!unitstr[strspn(unitstr, " ")]) { /* unitstr is blank */
5443 if (!nextunitstr) { /* terminal UNITSEPCHAR indicates repetition */
5444 freeunit(unit); /* of last unit and is permitted */
5445 return 0;
5446 }
5447 else { /* internal blank units are not allowed */
5448 if (printerror){
5449 showpointer(promptlen);
5450 puts("Error: blank unit not allowed");
5451 }
5452 freeunit(unit);
5453 return 1;
5454 }
5455 }
5456
5457 /* processunit() prints error messages; avoid it to supress them */
5458
5459 if ((printerror && processunit(unit+unitidx,unitstr,promptlen)) ||
5460 (!printerror &&
5461 (parseunit(unit+unitidx, unitstr,0,0)
5462 || completereduce(unit+unitidx)
5463 || compareunits(unit+unitidx,&one, ignore_primitive)))){
5464 if (printerror)
5465 printf("Error in unit list entry: %s\n",unitstr);
5466 freeunit(unit);
5467 freeunit(unit+1);
5468 return 1;
5469 }
5470
5471
5472 if (unitidx == 0)
5473 unitidx = 1;
5474 else {
5475 if (compareunits(unit, unit+1, ignore_dimless)){
5476 if (printerror){
5477 int wasverbose = flags.verbose;
5478 FILE *savelog = logfile;
5479 logfile=0;
5480 flags.verbose = 2; /* always use verbose form to be unambiguous */
5481 /* coverity[returned_null] */
5482 *(strchr(firstunitstr, UNITSEPCHAR)) = '\0';
5483 removespaces(firstunitstr);
5484 removespaces(unitstr);
5485 showpointer(promptlen);
5486 showconformabilityerr(firstunitstr, unit, unitstr, unit+1);
5487 flags.verbose = wasverbose;
5488 logfile = savelog;
5489 }
5490 freeunit(unit);
5491 freeunit(unit+1);
5492 return 1;
5493 }
5494 freeunit(unit+1);
5495 }
5496
5497 if (nextunitstr) {
5498 if (promptlen >= 0) promptlen += strwidth(unitstr)+1;
5499 *(nextunitstr++) = UNITSEPCHAR;
5500 }
5501 unitstr = nextunitstr;
5502 }
5503
5504 freeunit(unit);
5505
5506 return 0;
5507} /* end checkunitlist */
5508
5509
5510/*
5511 Call either processunit or checkunitlist, depending on whether the
5512 string 'unitstr' contains a separator character. Returns 0 on
5513 success and 1 on failure. If an error occurs an error message is
5514 printed to stdout.
5515
5516 A pointer will be printed if an error is detected, and promptlen
5517 should be set to the printing width of the prompt string, or set
5518 it to NOPOINT to supress printing of the pointer.
5519*/
5520
5521int
5522processwant(struct unittype *theunit, char *unitstr, int promptlen)
5523{
5524 if (flags.unitlists && strchr(unitstr, UNITSEPCHAR))
5525 return checkunitlist(unitstr, promptlen);
5526 else
5527 return processunit(theunit, unitstr, promptlen);
5528}
5529
5530
5531void
5532checkallaliases(int verbose)
5533{
5534 struct wantalias *aliasptr;
5535
5536 for(aliasptr = firstalias; aliasptr; aliasptr=aliasptr->next){
5537 if (verbose)
5538 printf("doing unit list '%s'\n", aliasptr->name);
5539 if (checkunitlist(aliasptr->definition,NOERRMSG))
5540 printf("Unit list '%s' contains errors\n", aliasptr->name);
5541 if (ulookup(aliasptr->name))
5542 printf("Unit list '%s' hides a unit definition.\n", aliasptr->name);
5543 if (fnlookup(aliasptr->name))
5544 printf("Unit list '%s' hides a function definition.\n", aliasptr->name);
5545 }
5546}
5547
5548
5549
5550/*
5551 Check that all units and prefixes are reducible to primitive units and that
5552 function definitions are valid and have correct inverses. A message is
5553 printed for every unit that does not reduce to primitive units.
5554
5555*/
5556
5557
5558void
5559checkunits(int verbosecheck)
5560{
5561 struct unittype have,second,one;
5562 struct unitlist *uptr;
5563 struct prefixlist *pptr;
5564 struct func *funcptr;
5565 char *prefixbuf, *testunit;
5566 int i, reduce_err;
5567 char *err;
5568
5569 initializeunit(&one);
5570
5571 /* Check all functions for valid definition and correct inverse */
5572
5573 for(i=0;i<SIMPLEHASHSIZE;i++)
5574 for(funcptr=ftab[i];funcptr;funcptr=funcptr->next)
5575 checkfunc(funcptr, verbosecheck);
5576
5577 checkallaliases(verbosecheck);
5578
5579 /* Now check all units for validity */
5580
5581 for(i=0;i<HASHSIZE;i++)
5582 for (uptr = utab[i]; uptr; uptr = uptr->next){
5583 if (verbosecheck)
5584 printf("doing '%s'\n",uptr->name);
5585 if (strchr(uptr->value, PRIMITIVECHAR))
5586 continue;
5587 else if (fnlookup(uptr->name))
5588 printf("Unit '%s' hidden by function '%s'\n", uptr->name, uptr->name);
5589 else if (parseunit(&have, uptr->value,&err,0))
5590 printf("'%s' defined as '%s': %s\n",uptr->name, uptr->value,err);
5591 else if ((reduce_err=completereduce(&have))){
5592 printf("'%s' defined as '%s' irreducible: %s",uptr->name, uptr->value,errormsg[reduce_err]);
5593 if (reduce_err==E_UNKNOWNUNIT && irreducible)
5594 printf(" '%s'", irreducible);
5595 putchar('\n');
5596 }
5597 else {
5598 parserflags.minusminus = !parserflags.minusminus;
5599 /* coverity[check_return] */
5600 parseunit(&second, uptr->name, 0, 0); /* coverity[check_return] */
5601 completereduce(&second); /* Can't fail because it worked above */
5602 if (compareunits(&have, &second, ignore_nothing)){
5603 printf("'%s': replace '-' with '+-' for subtraction or '*' to multiply\n", uptr->name);
5604 }
5605 freeunit(&second);
5606 parserflags.minusminus=!parserflags.minusminus;
5607 }
5608
5609 freeunit(&have);
5610 }
5611
5612 /* Check prefixes */
5613
5614 testunit="meter";
5615 for(i=0;i<SIMPLEHASHSIZE;i++)
5616 for(pptr = ptab[i]; pptr; pptr = pptr->next){
5617 if (verbosecheck)
5618 printf("doing '%s-'\n",pptr->name);
5619 prefixbuf = mymalloc(strlen(pptr->name) + strlen(testunit) + 1,
5620 "(checkunits)");
5621 strcpy(prefixbuf,pptr->name);
5622 strcat(prefixbuf,testunit);
5623 if (parseunit(&have, prefixbuf,0,0) || completereduce(&have) ||
5624 compareunits(&have,&one,ignore_primitive))
5625 printf("'%s-' defined as '%s' irreducible\n",pptr->name, pptr->value);
5626 else {
5627 int plevel; /* check for bad '/' character in prefix */
5628 char *ch;
5629 plevel = 0;
5630 for(ch=pptr->value;*ch;ch++){
5631 if (*ch==')') plevel--;
5632 else if (*ch=='(') plevel++;
5633 else if (plevel==0 && *ch=='/'){
5634 printf(
5635 "'%s-' defined as '%s' contains a bad '/'. (Add parentheses.)\n",
5636 pptr->name, pptr->value);
5637 break;
5638 }
5639 }
5640 }
5641 freeunit(&have);
5642 free(prefixbuf);
5643 }
5644}
5645
5646
5647/*
5648 Converts the input value 'havestr' (which is already parsed into
5649 the unit structure 'have') into a sum of the UNITSEPCHAR-separated
5650 units listed in 'wantstr'. You must call checkunitlist first to
5651 ensure 'wantstr' is error-free. Prints the results (or an error message)
5652 on stdout. Returns 0 on success and 1 on failure.
5653*/
5654
5655int
5656showunitlist(char *havestr, struct unittype *have, char *wantstr)
5657{
5658 struct unittype want, lastwant;
5659 char *lastunitstr, *nextunitstr, *lastwantstr=0;
5660 double remainder; /* portion of have->factor remaining */
5661 double round_dir; /* direction of rounding */
5662 double value; /* value (rounded to integer with 'r' option) */
5663 int firstunit = 1; /* first unit in a multi-unit string */
5664 int value_shown = 0; /* has a value been shown? */
5665 int sigdigits;
5666 char val_sign;
5667
5668 initializeunit(&want);
5669 remainder = fabs(have->factor);
5670 val_sign = have->factor < 0 ? '-' : '+';
5671 lastunitstr = 0;
5672 nextunitstr = 0;
5673 round_dir = 0;
5674
5675 if (flags.round) {
5676 /* disable unit repetition with terminal UNITSEPCHAR when rounding */
5677 if (lastchar(wantstr) == UNITSEPCHAR)
5678 lastchar(wantstr) = 0;
5679 if ((lastwantstr = strrchr(wantstr, UNITSEPCHAR)))
5680 lastwantstr++;
5681 }
5682
5683 while (wantstr) {
5684 if ((nextunitstr = strchr(wantstr, UNITSEPCHAR)))
5685 *(nextunitstr++) = '\0';
5686 removespaces(wantstr);
5687
5688 /*
5689 if wantstr ends in UNITSEPCHAR, repeat last unit--to give integer
5690 and fractional parts (3 oz + 0.371241 oz rather than 3.371241 oz)
5691 */
5692 if (emptystr(wantstr)) /* coverity[alias_transfer] */
5693 wantstr = lastunitstr;
5694
5695 if (processunit(&want, wantstr, NOPOINT)) {
5696 freeunit(&want);
5697 return 1;
5698 }
5699
5700 if (firstunit){
5701 /* checkunitlist() ensures conformability within 'wantstr',
5702 so we just need to check the first unit to see if it conforms
5703 to 'have' */
5704 if (compareunits(have, &want, ignore_dimless)) {
5705 showconformabilityerr(havestr, have, wantstr, &want);
5706 freeunit(&want);
5707 return 1;
5708 }
5709
5710 /* round to nearest integral multiple of last unit */
5711 if (flags.round) {
5712 value = remainder;
5713 if (nonempty(lastwantstr)) { /* more than one unit */
5714 removespaces(lastwantstr);
5715 initializeunit(&lastwant);
5716 if (processunit(&lastwant, lastwantstr, NOPOINT)) {
5717 freeunit(&lastwant);
5718 return 1;
5719 }
5720 remainder = round(remainder / lastwant.factor) * lastwant.factor;
5721 }
5722 else /* first unit is last unit */
5723 remainder = round(remainder / want.factor) * want.factor;
5724
5725 round_dir = remainder - value;
5726 }
5727 if (flags.verbose == 2) {
5728 removespaces(havestr);
5729 logprintf("\t%s = ", havestr);
5730 } else if (flags.verbose == 1)
5731 logputchar('\t');
5732 } /* end if first unit */
5733
5734 if (0==(sigdigits = getsigdigits(have->factor, remainder, 10)))
5735 break; /* nothing left */
5736
5737 /* Remove sub-precision junk accumulating in the remainder. Rounding
5738 is base 2 to ensure that we keep all valid bits. */
5739 remainder = round_digits(remainder,
5740 getsigdigits(have->factor,remainder,2),2);
5741 if (nextunitstr)
5742 remainder = want.factor * modf(remainder / want.factor, &value);
5743 else
5744 value = remainder / want.factor;
5745
5746 /* The remainder represents less than one of the current want unit.
5747 But with display rounding it may round up to 1, leading to an output
5748 like "4 feet + 12 inch". Check for this case and if the remainder
5749 indeed rounds up to 1 then add that remainder into the current unit
5750 and set the remainder to zero. */
5751 if (nextunitstr){
5752 double rounded_next =
5753 round_digits(remainder/want.factor,
5754 getsigdigits(have->factor, remainder / want.factor, 10),
5755 10);
5756 if (displays_as(1,rounded_next, NULL)){
5757 value++;
5758 remainder = 0; /* Remainder is zero */
5759 }
5760 }
5761
5762 /* Round the value to significant digits to prevent display
5763 of bogus sub-precision decimal digits */
5764 value = round_digits(value,sigdigits,10);
5765
5766 /* This ensures that testing value against zero will work below
5767 at the last unit, which is the only case where value is not integer */
5768 if (!nextunitstr && displays_as(0, value, NULL))
5769 value=0;
5770
5771 if (!flags.verbose){
5772 if (!firstunit)
5773 logputchar(UNITSEPCHAR);
5774 logprintf(num_format.format,value);
5775 value_shown=1;
5776 } else { /* verbose case */
5777 if (value != 0) {
5778 if (value_shown) /* have already displayed a number so need a '+' */
5779 logprintf(" %c ",val_sign);
5780 else if (val_sign=='-')
5781 logputs("-");
5782 showunitname(value, wantstr, PRINTNUM);
5783 if (sigdigits <= floor(log10(value))+1) /* Used all sig digits */
5784 logprintf(" (at %d-digit precision limit)", DBL_DIG);
5785 value_shown=1;
5786 }
5787 }
5788
5789 freeunit(&want);
5790 lastunitstr = wantstr;
5791 wantstr = nextunitstr;
5792 firstunit = 0;
5793 }
5794
5795 /* if the final unit value was rounded print indication */
5796 if (!value_shown) { /* provide output if every value rounded to zero */
5797 logputs("0 ");
5798 if (isdecimal(*lastunitstr))
5799 logputs("* ");
5800 logputs(lastunitstr);
5801 }
5802
5803 if (round_dir != 0) {
5804 if (flags.verbose){
5805 if (round_dir > 0)
5806 logprintf(" (rounded up to nearest %s) ", lastunitstr);
5807 else
5808 logprintf(" (rounded down to nearest %s) ", lastunitstr);
5809 } else
5810 logprintf("%c%c", UNITSEPCHAR, round_dir > 0 ?'-':'+');
5811 }
5812 logputchar('\n');
5813 return 0;
5814} /* end showunitlist */
5815
5816
5817#if defined (_WIN32) && defined (HAVE_MKS_TOOLKIT)
5818int
5819ismksmore(char *pager)
5820{
5821 static int mksmore = -1;
5822
5823 if (mksmore >= 0)
5824 return mksmore;
5825
5826 /*
5827 Tries to determine whether the MKS Toolkit version of more(1) or
5828 less(1) will run. In older versions of the Toolkit, neither
5829 accepted '+<lineno>', insisting on the POSIX-compliant '+<lineno>g'.
5830 */
5831 if (strstr(pager, "more") || strstr(pager, "less")) {
5832 char *mypager, *mkspager, *mksroot, *p;
5833 char pathbuf[FILENAME_MAX + 1];
5834 struct _stat mybuf, mksbuf;
5835
5836 mypager = NULL;
5837 mkspager = NULL;
5838 mksmore = 0;
5839 if (strlen(pager) > FILENAME_MAX) {
5840 fprintf(stderr, "%s: cannot invoke pager--value '%s' in PAGER too long\n",
5841 progname, pager);
5842 return 0; /* TODO: this really isn't the right value */
5843 }
5844 else if (!isfullpath(pager)) {
5845 mypager = (char *) mymalloc(strlen(pager) + strlen(EXE_EXT) + 1, "(ishelpquery)");
5846 strcpy(mypager, pager);
5847 if (!((p = strrchr(mypager, '.')) && isexe(p)))
5848 strcat(mypager, EXE_EXT);
5849
5850 _searchenv(mypager, "PATH", pathbuf);
5851 }
5852 else
5853 strcpy(pathbuf, pager);
5854
5855 mksroot = getenv("ROOTDIR");
5856 if (mksroot) {
5857 char * mksprog;
5858
5859 if (strstr(pager, "more"))
5860 mksprog = "more.exe";
5861 else
5862 mksprog = "less.exe";
5863 mkspager = (char *) mymalloc(strlen(mksroot) + strlen("/mksnt/") + strlen(mksprog) + 1,
5864 "(ishelpquery)");
5865 strcpy(mkspager, mksroot);
5866 strcat(mkspager, "\\mksnt\\");
5867 strcat(mkspager, mksprog);
5868 }
5869
5870 if (*pathbuf && mksroot) {
5871 if (_stat(mkspager, &mksbuf)) {
5872 fprintf(stderr, "%s: cannot stat file '%s'. ", progname, mkspager);
5873 perror((char *) NULL);
5874 return 0;
5875 }
5876 if (_stat(pathbuf, &mybuf)) {
5877 fprintf(stderr, "%s: cannot stat file '%s'. ", progname, pathbuf);
5878 perror((char *) NULL);
5879 return 0;
5880 }
5881 /*
5882 if we had inodes, this would be simple ... but if it walks
5883 like a duck and swims like a duck and quacks like a duck ...
5884 */
5885 if (mybuf.st_size == mksbuf.st_size
5886 && mybuf.st_ctime == mksbuf.st_ctime
5887 && mybuf.st_mtime == mksbuf.st_mtime
5888 && mybuf.st_atime == mksbuf.st_atime
5889 && mybuf.st_mode == mksbuf.st_mode)
5890 mksmore = 1;
5891 }
5892 if (mypager)
5893 free(mypager);
5894 if (mkspager)
5895 free(mkspager);
5896 }
5897
5898 return mksmore;
5899}
5900#endif
5901
5902/*
5903 Checks to see if the input string contains HELPCOMMAND possibly
5904 followed by a unit name on which help is sought. If not, then
5905 return 0. Otherwise invoke the pager on units file at the line
5906 where the specified unit is defined. Then return 1. */
5907
5908int
5909ishelpquery(char *str, struct unittype *have)
5910{
5911 struct unitlist *unit;
5912 struct func *function;
5913 struct wantalias *alias;
5914 struct prefixlist *prefix;
5915 char commandbuf[1000]; /* Hopefully this is enough overkill as no bounds */
5916 int unitline; /* checking is performed. */
5917 char *file;
5918 char **exitptr;
5919 char *commandstr; /* command string varies with OS */
5920
5921 if (have && !strcmp(str, UNITMATCH)){
5922 tryallunits(have,0);
5923 return 1;
5924 }
5925 for(exitptr=exit_commands;*exitptr;exitptr++)
5926 if (!strcmp(str, *exitptr))
5927 exit(EXIT_SUCCESS);
5928 if (startswith(str, SEARCHCOMMAND)){
5929 str+=strlen(SEARCHCOMMAND);
5930 if (!emptystr(str) && *str != ' ')
5931 return 0;
5932 removespaces(str);
5933 if (emptystr(str)){
5934 printf("\n\
5935Type 'search text' to see a list of all unit names \n\
5936containing 'text' as a substring\n\n");
5937 return 1;
5938 }
5939 tryallunits(0,str);
5940 return 1;
5941 }
5942 if (startswith(str, HELPCOMMAND)){
5943 str+=strlen(HELPCOMMAND);
5944 if (!emptystr(str) && *str != ' ')
5945 return 0;
5946 removespaces(str);
5947
5948 if (emptystr(str)){
5949 int nlines;
5950 char *unitsfile;
5951 char *msg = "\n\
5952%s converts between different measuring systems and %s6 inches\n\
5953acts as a units-aware calculator. At the '%s' %scm\n\
5954prompt, type in the units you want to convert from or * 15.24\n\
5955an expression to evaluate. At the '%s' prompt, / 0.065\n\
5956enter the units to convert to or press return to see\n\
5957the reduced form or definition. %stempF(75)\n\
5958 %stempC\n\
5959The first example shows that 6 inches is about 15 cm 23.889\n\
5960or (1/0.065) cm. The second example shows how to\n\
5961convert 75 degrees Fahrenheit to Celsius. The third %sbu^(1/3)\n\
5962example converts the cube root of a bushel to a list %sft;in\n\
5963of semicolon-separated units. 1 ft + 0.9 in\n\
5964\n\
5965To quit from %s type 'quit' or 'exit'. %s2 btu + 450 ft lbf\n\
5966 %s(kg^2/s)/(day lb/m^2)\n\
5967At the '%s' prompt type '%s' to get a * 1.0660684e+08\n\
5968list of conformable units. At either prompt you / 9.3802611e-09\n\
5969type 'help myunit' to browse the units database\n\
5970and read the comments relating to myunit or see %s6 tbsp sugar\n\
5971other units related to myunit. Typing 'search %sg\n\
5972text' will show units whose names contain 'text'. * 75\n\
5973 / 0.013333333\n\n";
5974 char *fmsg = "To learn about the available units look in '%s'\n\n";
5975 FILE *fp;
5976
5977 /* presumably, this cannot fail because it was already checked at startup */
5978 unitsfile = findunitsfile(NOERRMSG);
5979
5980 nlines = countlines(msg);
5981 /* but check again anyway ... */
5982 if (unitsfile)
5983 nlines += countlines(fmsg);
5984
5985 /* see if we need a pager */
5986 fp = get_output_fp(nlines);
5987
5988 fprintf(fp, msg,
5989 progname, QUERYHAVE,
5990 QUERYHAVE, QUERYWANT,
5991 QUERYWANT,
5992 QUERYHAVE,QUERYWANT,QUERYHAVE,QUERYWANT,
5993 progname, QUERYHAVE,QUERYWANT,
5994 QUERYWANT,
5995 UNITMATCH,
5996 QUERYHAVE,QUERYWANT);
5997
5998 if (unitsfile)
5999 fprintf(fp, fmsg, unitsfile);
6000
6001 if (fp != stdout)
6002 pclose(fp);
6003
6004 return 1;
6005 }
6006 if ((function = fnlookup(str))){
6007 file = function->file;
6008 unitline = function->linenumber;
6009 }
6010 else if ((unit = ulookup(str))){
6011 unitline = unit->linenumber;
6012 file = unit->file;
6013 }
6014 else if ((prefix = plookup(str)) && strlen(str)==prefix->len){
6015 unitline = prefix->linenumber;
6016 file = prefix->file;
6017 }
6018 else if ((alias = aliaslookup(str))){
6019 unitline = alias->linenumber;
6020 file = alias->file;
6021 }
6022 else {
6023 printf("Unknown unit '%s'\n",str);
6024 return 1;
6025 }
6026
6027 /*
6028 With Microsoft compilers, system() uses cmd.exe.
6029 Inner escaped quotes are necessary for filenames with spaces; outer
6030 escaped quotes are necessary for cmd.exe to see the command as a
6031 single string containing one or more quoted strings
6032 (e.g., cmd /c ""command" "arg1" "arg2" ... ")
6033 */
6034#if defined (_WIN32)
6035 #if defined (HAVE_MKS_TOOLKIT)
6036 if (strstr(pager, "pg"))
6037 commandstr = "\"\"%s\" +%d \"%s\"\"";
6038 else if (ismksmore(pager)) {
6039 /*
6040 use the POSIX-compliant '+<number>g' for compatibility with older
6041 versions of the Toolkit that don't accept '+<number>'
6042 */
6043 commandstr = "\"\"%s\" +%dg \"%s\"\"";
6044 }
6045 else {
6046 /*
6047 more.com apparently takes the number as an offset rather than a
6048 line number, so that '+1' starts at line 2 of the file.
6049 more.com also cannot back up, so allow two lines of preceding
6050 context.
6051 */
6052 unitline -= 3;
6053 if (unitline < 0)
6054 unitline = 0;
6055 commandstr = "\"\"%s\" +%d \"%s\"\"";
6056 }
6057 #else /* more.com is probably the only option */
6058 unitline -= 3;
6059 if (unitline < 0)
6060 unitline = 0;
6061 commandstr = "\"\"%s\" +%d \"%s\"\"";
6062 #endif
6063#else /* *nix */
6064 commandstr = "%s +%d %s";
6065#endif
6066 sprintf(commandbuf, commandstr, pager, unitline, file);
6067 if (system(commandbuf))
6068 fprintf(stderr,"%s: cannot invoke pager '%s' to display help\n",
6069 progname, pager);
6070 return 1;
6071 }
6072 return 0;
6073}
6074
6075
6076#ifdef SUPPORT_UTF8
6077void
6078checklocale()
6079{
6080 char *temp;
6081 temp = setlocale(LC_CTYPE,"");
6082 utf8mode = (strcmp(nl_langinfo(CODESET),"UTF-8")==0);
6083 if (temp){
6084 mylocale = dupstr(temp);
6085 temp = strchr(mylocale,'.');
6086 if (temp)
6087 *temp = 0;
6088 } else
6089 mylocale = DEFAULTLOCALE;
6090}
6091
6092#else
6093
6094void
6095checklocale()
6096{
6097 char *temp=0;
6098 /* environment overrides system */
6099 temp = getenv("LC_CTYPE");
6100 if (!temp)
6101 temp = getenv("LC_ALL");
6102 if (!temp)
6103 temp = getenv("LANG");
6104#ifndef NO_SETLOCALE
6105 if (temp)
6106 temp = setlocale(LC_CTYPE,temp);
6107 if (!temp)
6108 temp = setlocale(LC_CTYPE,""); /* try system default */
6109#endif
6110 if (!temp)
6111 mylocale = DEFAULTLOCALE;
6112 else {
6113 mylocale = dupstr(temp);
6114 temp = strchr(mylocale,'.');
6115 if (temp)
6116 *temp = 0;
6117 }
6118}
6119
6120#endif
6121
6122/*
6123 Replaces an alias in the specified string input. Returns 1 if the
6124 alias that is found contains errors.
6125*/
6126
6127int
6128replacealias(char **string, int *buflen)
6129{
6130 int usefree = 1;
6131 struct wantalias *aliasptr;
6132 char *input;
6133
6134 if (!flags.readline && buflen)
6135 usefree = 0;
6136
6137 if (nonempty(*string)) { /* check that string is defined and nonempty */
6138 input = *string;
6139 removespaces(input);
6140 if ((aliasptr=aliaslookup(input))){
6141 if (checkunitlist(aliasptr->definition,NOERRMSG)){
6142 puts("Unit list definition contains errors.");
6143 return 1;
6144 }
6145 if (usefree){
6146 free(*string);
6147 *string = dupstr(aliasptr->definition);
6148 } else { /* coverity[dead_error_line] */
6149 while (strlen(aliasptr->definition)>*buflen)
6150 growbuffer(string, buflen);
6151 strcpy(*string, aliasptr->definition);
6152 }
6153 }
6154 }
6155 return 0;
6156}
6157
6158
6159/*
6160 Remaps the locale name returned on Windows systems to the value
6161 returned on Unix-like systems
6162*/
6163
6164void
6165remaplocale(char *filename)
6166{
6167 FILE *map;
6168 char *value;
6169 char name[80];
6170
6171 map = openfile(filename,"rt");
6172 if (!map) {
6173 fprintf(stderr,"%s: cannot open locale map '%s'. ",progname,filename);
6174 perror((char *) NULL);
6175 }
6176 else {
6177 while(!feof(map)){
6178 if (!fgets(name,80,map))
6179 break;
6180 lastchar(name) = 0;
6181 value=strchr(name,'#');
6182 if (value) *value=0;
6183 value=strchr(name,'\t');
6184 if (!value) continue;
6185 *value++=0;
6186 removespaces(value);
6187 removespaces(name);
6188 if (!strcmp(name, mylocale))
6189 mylocale = dupstr(value);
6190 }
6191 fclose(map);
6192 }
6193}
6194
6195
6196void
6197close_logfile(void)
6198{
6199 if (logfile){
6200 fputc('\n',logfile);
6201 fclose(logfile);
6202 }
6203}
6204
6205
6206void
6207open_logfile(void)
6208{
6209 time_t logtime;
6210 char * timestr;
6211
6212 logfile = openfile(logfilename, "at");
6213 if (!logfile){
6214 fprintf(stderr, "%s: cannot write to log file '%s'. ",
6215 progname, logfilename);
6216 perror(0);
6217 exit(EXIT_FAILURE);
6218 }
6219 time(&logtime);
6220 timestr = ctime(&logtime);
6221 fprintf(logfile, "### Log started %s \n", timestr);
6222 atexit(close_logfile);
6223}
6224
6225void
6226write_files_sig(int sig)
6227{
6228#ifdef READLINE
6229 if (historyfile)
6230 save_history();
6231#endif
6232 close_logfile();
6233 signal(sig, SIG_DFL);
6234 raise(sig);
6235}
6236
6237
6238
6239int test_int(int a, int b)
6240{
6241 return a + b;
6242}
6243
6244char *g_argv_0 = NULL;
6245
6246
6247void conversion_setup();
6248
6249#define exit return
6250int
6251conversion_worker(char *havestr, char *wantstr)
6252{
6253 struct func *funcval;
6254 struct wantalias *alias;
6255 struct unittype have;
6256 struct unittype want;
6257
6258 conversion_setup();
6259
6260 replacectrlchars(havestr);
6261 if (wantstr)
6262 replacectrlchars(wantstr);
6263#ifdef SUPPORT_UTF8
6264 if (strwidth(havestr)<0){
6265 printf("Error: %s on input\n",invalid_utf8);
6266 exit(EXIT_FAILURE);
6267 }
6268 if (wantstr && strwidth(wantstr)<0){
6269 printf("Error: %s on input\n",invalid_utf8);
6270 exit(EXIT_FAILURE);
6271 }
6272#endif
6273 replace_operators(havestr);
6274 removespaces(havestr);
6275 if (wantstr) {
6276 replace_operators(wantstr);
6277 removespaces(wantstr);
6278 }
6279 if ((funcval = fnlookup(havestr))){
6280 showfuncdefinition(funcval, FUNCTION);
6281 exit(EXIT_SUCCESS);
6282 }
6283 if ((funcval = invfnlookup(havestr))){
6284 showfuncdefinition(funcval, INVERSE);
6285 exit(EXIT_SUCCESS);
6286 }
6287
6288 if ((alias = aliaslookup(havestr))){
6289 showunitlistdef(alias);
6290 exit(EXIT_SUCCESS);
6291 }
6292 if (processunit(&have, havestr, NOPOINT))
6293 exit(EXIT_FAILURE);
6294 if (flags.showconformable == 1) {
6295 tryallunits(&have,0);
6296 exit(EXIT_SUCCESS);
6297 }
6298 if (!wantstr){
6299 showdefinition(havestr,&have);
6300 exit(EXIT_SUCCESS);
6301 }
6302 if (replacealias(&wantstr, 0)) /* the 0 says that we can free wantstr */
6303 exit(EXIT_FAILURE);
6304 if ((funcval = fnlookup(wantstr))){
6305 if (showfunc(havestr, &have, funcval)) /* Clobbers have */
6306 exit(EXIT_FAILURE);
6307 else
6308 exit(EXIT_SUCCESS);
6309 }
6310 if (processwant(&want, wantstr, NOPOINT))
6311 exit(EXIT_FAILURE);
6312 if (strchr(wantstr, UNITSEPCHAR)){
6313 if (showunitlist(havestr, &have, wantstr))
6314 exit(EXIT_FAILURE);
6315 else
6316 exit(EXIT_SUCCESS);
6317 }
6318 if (showanswer(havestr,&have,wantstr,&want))
6319 exit(EXIT_FAILURE);
6320 else
6321 exit(EXIT_SUCCESS);
6322}
6323#undef exit
6324
6325
6326void
6327conversion_setup()
6328{
6329 static int setup = 0;
6330
6331 if (setup)
6332 return;
6333
6334 setup = 1;
6335
6336 flags.quiet = 1; /* Do not supress prompting */
6337 flags.unitcheck = 0; /* Unit checking is off */
6338 flags.verbose = 2; /* Medium verbosity */
6339 flags.round = 0; /* Rounding off */
6340 flags.strictconvert=0; /* Strict conversion disabled (reciprocals active) */
6341 flags.unitlists = 1; /* Multi-unit conversion active */
6342 flags.oneline = 1; /* One line output is disabled */
6343 flags.showconformable=0; /* show unit conversion rather than all conformable units */
6344 flags.showfactor = 0; /* Don't show a multiplier for a 1|x fraction */
6345 /* in unit list output */
6346 parserflags.minusminus = 1; /* '-' character gives subtraction */
6347 parserflags.oldstar = 0; /* '*' has same precedence as '/' */
6348
6349 progname = getprogramname(g_argv_0);
6350
6351
6352
6353 if (!(isfullpath(UNITSFILE) && isfullpath(LOCALEMAP)))
6354 progdir = getprogdir(g_argv_0, &fullprogname);
6355 else {
6356 progdir = NULL;
6357 fullprogname = NULL;
6358 }
6359 datadir = getdatadir(); /* directory to search as last resort */
6360
6361 checklocale();
6362
6363 homedir = findhome(&homedir_error);
6364
6365 unitsfiles[0] = 0;
6366
6367 if (!unitsfiles[0]){
6368 char *unitsfile;
6369 unitsfile = findunitsfile(ERRMSG);
6370 if (!unitsfile)
6371 exit(EXIT_FAILURE);
6372 else {
6373 int file_exists;
6374
6375 unitsfiles[0] = unitsfile;
6376 unitsfiles[1] = personalfile(HOME_UNITS_ENV,homeunitsfile,
6377 0, &file_exists);
6378 unitsfiles[2] = 0;
6379 }
6380 }
6381
6382 char **unitfileptr;
6383 for(unitfileptr=unitsfiles;*unitfileptr;unitfileptr++){
6384 int unitcount, prefixcount, funccount;
6385 int readerr = readunits(*unitfileptr, stderr, &unitcount, &prefixcount,
6386 &funccount, 0);
6387 if (readerr==E_MEMORY || readerr==E_FILE)
6388 exit(EXIT_FAILURE);
6389 }
6390}
6391
6392
6393void
6394do_a_conversion(char *input, char *output)
6395{
6396 char *inp, *out;
6397
6398 char units[] = "./units";
6399
6400 g_argv_0 = units;
6401
6402 checklocale();
6403
6404 num_format.format = NULL;
6405 num_format.precision = DEFAULTPRECISION;
6406 num_format.type = DEFAULTTYPE;
6407
6408 if (num_format.format != NULL) {
6409 if (parsenumformat())
6410 exit(EXIT_FAILURE);
6411 }
6412 else
6413 setnumformat();
6414
6415 conversion_worker(input, output);
6416}
6417
6418
6419int test_main()
6420{
6421 char a[] = "pi", b[] = "";
6422 do_a_conversion(a, b);
6423 do_a_conversion(a, b);
6424 do_a_conversion(a, b);
6425}
6426
6427
6428int
6429old_main(int argc, char **argv)
6430{
6431 static struct unittype have, want;
6432 char *havestr=0, *wantstr=0;
6433 int leading_spaces;
6434 struct func *funcval;
6435 struct wantalias *alias;
6436 int havestrsize=0; /* Only used if READLINE is undefined */
6437 int wantstrsize=0; /* Only used if READLINE is undefined */
6438 int readerr;
6439 char **unitfileptr;
6440 int unitcount=0, prefixcount=0, funccount=0; /* for counting units */
6441 char *queryhave, *querywant, *comment;
6442 int queryhavewidth, querywantwidth;
6443#ifdef _WIN32
6444 char *localemap;
6445#endif
6446
6447 /* Set program parameter defaults */
6448 num_format.format = NULL;
6449 num_format.precision = DEFAULTPRECISION;
6450 num_format.type = DEFAULTTYPE;
6451
6452 flags.quiet = 0; /* Do not supress prompting */
6453 flags.unitcheck = 0; /* Unit checking is off */
6454 flags.verbose = 1; /* Medium verbosity */
6455 flags.round = 0; /* Rounding off */
6456 flags.strictconvert=0; /* Strict conversion disabled (reciprocals active) */
6457 flags.unitlists = 1; /* Multi-unit conversion active */
6458 flags.oneline = 0; /* One line output is disabled */
6459 flags.showconformable=0; /* show unit conversion rather than all conformable units */
6460 flags.showfactor = 0; /* Don't show a multiplier for a 1|x fraction */
6461 /* in unit list output */
6462 parserflags.minusminus = 1; /* '-' character gives subtraction */
6463 parserflags.oldstar = 0; /* '*' has same precedence as '/' */
6464
6465 progname = getprogramname(argv[0]);
6466
6467 /*
6468 unless UNITSFILE and LOCALEMAP have absolute pathnames, we may need
6469 progdir to search for supporting files
6470 */
6471 if (!(isfullpath(UNITSFILE) && isfullpath(LOCALEMAP)))
6472 progdir = getprogdir(argv[0], &fullprogname);
6473 else {
6474 progdir = NULL;
6475 fullprogname = NULL;
6476 }
6477 datadir = getdatadir(); /* directory to search as last resort */
6478
6479 checklocale();
6480
6481 homedir = findhome(&homedir_error);
6482
6483#ifdef READLINE
6484 # if RL_READLINE_VERSION > 0x0402
6485 rl_completion_entry_function = (rl_compentry_func_t *)completeunits;
6486# else
6487 rl_completion_entry_function = (Function *)completeunits;
6488# endif
6489 rl_basic_word_break_characters = " \t+-*/()|^;";
6490 flags.readline = isatty(0);
6491 if (flags.readline){
6492 int file_exists;
6493 historyfile = personalfile(NULL,HISTORY_FILE,1,&file_exists);
6494 }
6495#else
6496 flags.readline = 0;
6497#endif
6498
6499 unitsfiles[0] = 0;
6500
6501#ifdef _WIN32
6502 if (!strcmp(homeunitsfile,".units"))
6503 homeunitsfile = "unitdef.units";
6504#endif
6505
6506 pager = getenv("PAGER");
6507 if (!pager)
6508 pager = DEFAULTPAGER;
6509
6510 flags.interactive = processargs(argc, argv, &havestr, &wantstr);
6511
6512#ifdef READLINE
6513 if (flags.interactive && flags.readline && historyfile){
6514 rl_initialize();
6515 read_history(historyfile);
6516 init_history_length = history_length;
6517 init_history_base = history_base;
6518 atexit(save_history);
6519 }
6520#endif
6521
6522 signal(SIGINT, write_files_sig);
6523 signal(SIGTERM, write_files_sig);
6524#ifdef SIGQUIT
6525 signal(SIGQUIT, SIG_IGN); /* Ignore QUIT signal sent by Ctrl-\ or Ctrl-4 */
6526#endif
6527 if (logfilename) {
6528 if (!flags.interactive)
6529 fprintf(stderr,
6530 "Log file '%s' ignored in non-interactive mode.\n",logfilename);
6531 else open_logfile();
6532 }
6533
6534 /* user has specified the complete format--use it */
6535 if (num_format.format != NULL) {
6536 if (parsenumformat())
6537 exit(EXIT_FAILURE);
6538 }
6539 else
6540 setnumformat();
6541
6542 if (flags.verbose==0)
6543 deftext = "";
6544
6545 if (!unitsfiles[0]){
6546 char *unitsfile;
6547 unitsfile = findunitsfile(ERRMSG);
6548 if (!unitsfile)
6549 exit(EXIT_FAILURE);
6550 else {
6551 int file_exists;
6552
6553 unitsfiles[0] = unitsfile;
6554 unitsfiles[1] = personalfile(HOME_UNITS_ENV,homeunitsfile,
6555 0, &file_exists);
6556 unitsfiles[2] = 0;
6557 }
6558 }
6559
6560#ifdef _WIN32
6561 localemap = findlocalemap(ERRMSG);
6562 if (localemap)
6563 remaplocale(localemap);
6564#endif
6565
6566 for(unitfileptr=unitsfiles;*unitfileptr;unitfileptr++){
6567 readerr = readunits(*unitfileptr, stderr, &unitcount, &prefixcount,
6568 &funccount, 0);
6569 if (readerr==E_MEMORY || readerr==E_FILE)
6570 exit(EXIT_FAILURE);
6571 }
6572
6573 if (flags.quiet)
6574 queryhave = querywant = ""; /* No prompts are being printed */
6575 else {
6576 if (!promptprefix){
6577 queryhave = QUERYHAVE;
6578 querywant = QUERYWANT;
6579 } else {
6580 queryhave = (char *)mymalloc(strlen(promptprefix)+strlen(QUERYHAVE)+1,
6581 "(main)");
6582 querywant = (char *)mymalloc(strlen(promptprefix)+strlen(QUERYWANT)+1,
6583 "(main)");
6584 strcpy(queryhave, promptprefix);
6585 strcat(queryhave, QUERYHAVE);
6586 memset(querywant, ' ', strlen(promptprefix));
6587 strcpy(querywant+strlen(promptprefix), QUERYWANT);
6588 }
6589 printf("%d units, %d prefixes, %d nonlinear units\n\n",
6590 unitcount, prefixcount,funccount);
6591 }
6592 queryhavewidth = strwidth(queryhave);
6593 querywantwidth = strwidth(querywant);
6594
6595 if (flags.unitcheck) {
6596 checkunits(flags.unitcheck==2 || flags.verbose==2);
6597 exit(EXIT_SUCCESS);
6598 }
6599
6600 if (!flags.interactive) {
6601 replacectrlchars(havestr);
6602 if (wantstr)
6603 replacectrlchars(wantstr);
6604#ifdef SUPPORT_UTF8
6605 if (strwidth(havestr)<0){
6606 printf("Error: %s on input\n",invalid_utf8);
6607 exit(EXIT_FAILURE);
6608 }
6609 if (wantstr && strwidth(wantstr)<0){
6610 printf("Error: %s on input\n",invalid_utf8);
6611 exit(EXIT_FAILURE);
6612 }
6613#endif
6614 replace_operators(havestr);
6615 removespaces(havestr);
6616 if (wantstr) {
6617 replace_operators(wantstr);
6618 removespaces(wantstr);
6619 }
6620 if ((funcval = fnlookup(havestr))){
6621 showfuncdefinition(funcval, FUNCTION);
6622 exit(EXIT_SUCCESS);
6623 }
6624 if ((funcval = invfnlookup(havestr))){
6625 showfuncdefinition(funcval, INVERSE);
6626 exit(EXIT_SUCCESS);
6627 }
6628 if ((alias = aliaslookup(havestr))){
6629 showunitlistdef(alias);
6630 exit(EXIT_SUCCESS);
6631 }
6632 if (processunit(&have, havestr, NOPOINT))
6633 exit(EXIT_FAILURE);
6634 if (flags.showconformable == 1) {
6635 tryallunits(&have,0);
6636 exit(EXIT_SUCCESS);
6637 }
6638 if (!wantstr){
6639 showdefinition(havestr,&have);
6640 exit(EXIT_SUCCESS);
6641 }
6642 if (replacealias(&wantstr, 0)) /* the 0 says that we can free wantstr */
6643 exit(EXIT_FAILURE);
6644 if ((funcval = fnlookup(wantstr))){
6645 if (showfunc(havestr, &have, funcval)) /* Clobbers have */
6646 exit(EXIT_FAILURE);
6647 else
6648 exit(EXIT_SUCCESS);
6649 }
6650 if (processwant(&want, wantstr, NOPOINT))
6651 exit(EXIT_FAILURE);
6652 if (strchr(wantstr, UNITSEPCHAR)){
6653 if (showunitlist(havestr, &have, wantstr))
6654 exit(EXIT_FAILURE);
6655 else
6656 exit(EXIT_SUCCESS);
6657 }
6658 if (showanswer(havestr,&have,wantstr,&want))
6659 exit(EXIT_FAILURE);
6660 else
6661 exit(EXIT_SUCCESS);
6662 } else {
6663 /******************/
6664 /** interactive **/
6665 /******************/
6666 for (;;) {
6667 do {
6668 fflush(stdout);
6669 getuser(&havestr,&havestrsize,queryhave);
6670 replace_operators(havestr);
6671 comment = strip_comment(havestr);
6672 leading_spaces = strspn(havestr," ");
6673 removespaces(havestr);
6674 if (logfile && comment && emptystr(havestr))
6675 fprintf(logfile, "#%s\n", comment);
6676 } while (emptystr(havestr)
6677 || ishelpquery(havestr,0)
6678 || definevariable(havestr,queryhavewidth+leading_spaces)
6679 || (!fnlookup(havestr)
6680 && !invfnlookup(havestr)
6681 && !aliaslookup(havestr)
6682 && processunit(&have, havestr, queryhavewidth+leading_spaces)));
6683 if (logfile) {
6684 if (comment)
6685 fprintf(logfile, "%s%s\t#%s\n", LOGFROM, havestr,comment);
6686 else
6687 fprintf(logfile, "%s%s\n", LOGFROM, havestr);
6688 }
6689 if ((alias = aliaslookup(havestr))){
6690 showunitlistdef(alias);
6691 continue;
6692 }
6693 if ((funcval = fnlookup(havestr))){
6694 showfuncdefinition(funcval, FUNCTION);
6695 continue;
6696 }
6697 if ((funcval = invfnlookup(havestr))){
6698 showfuncdefinition(funcval, INVERSE);
6699 continue;
6700 }
6701 do {
6702 int repeat;
6703 do {
6704 repeat = 0;
6705 fflush(stdout);
6706 getuser(&wantstr,&wantstrsize,querywant);
6707 replace_operators(wantstr);
6708 comment = strip_comment(wantstr);
6709 leading_spaces = strspn(wantstr," ");
6710 removespaces(wantstr);
6711 if (logfile && comment && emptystr(wantstr)){
6712 fprintf(logfile, "#%s\n", comment);
6713 repeat = 1;
6714 }
6715 if (ishelpquery(wantstr, &have)){
6716 repeat = 1;
6717 printf("%s%s\n",queryhave, havestr);
6718 }
6719 } while (repeat);
6720 } while (replacealias(&wantstr, &wantstrsize)
6721 || (!fnlookup(wantstr)
6722 && processwant(&want, wantstr, querywantwidth+leading_spaces)));
6723 if (logfile) {
6724 fprintf(logfile, "%s", LOGTO);
6725 tightprint(logfile, wantstr);
6726 if (comment)
6727 fprintf(logfile, "\t#%s", comment);
6728 putc('\n', logfile);
6729 }
6730 if (emptystr(wantstr))
6731 showdefinition(havestr,&have);
6732 else if (strchr(wantstr, UNITSEPCHAR))
6733 showunitlist(havestr, &have, wantstr);
6734 else if ((funcval = fnlookup(wantstr)))
6735 showfunc(havestr, &have, funcval); /* Clobbers have */
6736 else {
6737 showanswer(havestr,&have,wantstr, &want);
6738 freeunit(&want);
6739 }
6740 unitcopy(&lastunit, &have);
6741 lastunitset=1;
6742 freeunit(&have);
6743 }
6744 }
6745 return (0);
6746}
6747
6748/* NOTES:
6749
6750mymalloc, growbuffer and tryallunits are the only places with print
6751statements that should (?) be removed to make a library. How can
6752error reporting in these functions (memory allocation errors) be
6753handled cleanly for a library implementation?
6754
6755Way to report the reduced form of the two operands of a sum when
6756they are not conformable.
6757
6758Way to report the required domain when getting an domain error.
6759
6760*/