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