1 //
2 // srecord - manipulate eprom load files
3 // Copyright (C) 1998, 1999, 2002, 2003, 2006-2013 Peter Miller
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU Lesser General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or (at
8 // your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 //
18 
19 #include <cassert>
20 #include <cctype>
21 #include <cstring>
22 #include <cstdio>
23 #include <cstdlib>
24 #include <errno.h>
25 #include <iostream>
26 #include <unistd.h>
27 
28 #include <srecord/arglex.h>
29 #include <srecord/progname.h>
30 #include <srecord/quit.h>
31 #include <srecord/versn_stamp.h>
32 
33 // Cygwin's mingw has the execvp prototype in the wrong place.
34 #ifdef __MSVCRT__
35 #include <process.h>
36 #endif
37 
38 static const srecord::arglex::table_ty default_table[] =
39 {
40     { "-",            srecord::arglex::token_stdio,       },
41     { "-Help",        srecord::arglex::token_help,        },
42     { "-LICense",     srecord::arglex::token_license,     },
43     { "-Page_Length", srecord::arglex::token_page_length, },
44     { "-Page_Width",  srecord::arglex::token_page_width,  },
45     { "-TRACIng",     srecord::arglex::token_tracing,     },
46     { "-Verbose",     srecord::arglex::token_verbose,     },
47     { "-VERSion",     srecord::arglex::token_version,     },
48     SRECORD_ARGLEX_END_MARKER
49 };
50 
51 
arglex()52 srecord::arglex::arglex() :
53     usage_tail_(0)
54 {
55     table_set(default_table);
56 }
57 
58 
arglex(int ac,char ** av)59 srecord::arglex::arglex(int ac, char **av) :
60     usage_tail_(0)
61 {
62     progname_set(av[0]);
63     for (int j = 1; j < ac; ++j)
64     {
65         if (av[j][0] == '@')
66             read_arguments_file(av[j] + 1);
67         else
68             arguments.push_back(av[j]);
69     }
70     table_set(default_table);
71 }
72 
73 
74 void
read_arguments_file(const char * filename)75 srecord::arglex::read_arguments_file(const char *filename)
76 {
77     FILE *fp = fopen(filename, "r");
78     if (!fp)
79         quit_default.fatal_error_errno("open \"%s\"", filename);
80     for (;;)
81     {
82         int sc = getc(fp);
83         if (sc == EOF)
84             break;
85         unsigned char c = sc;
86 
87         //
88         // Ignore white space between words.
89         //
90         if (isspace(c))
91             continue;
92 
93         //
94         // Ignore comments
95         //
96         if (c == '#')
97         {
98             for (;;)
99             {
100                 sc = getc(fp);
101                 if (sc == EOF || sc == '\n')
102                     break;
103             }
104             continue;
105         }
106         char buffer[1000];
107         char *bp = buffer;
108         for (;;)
109         {
110             if (bp < buffer + sizeof(buffer) - 1)
111                 *bp++ = c;
112             sc = getc(fp);
113             if (sc == EOF)
114                 break;
115             c = sc;
116             if (isspace(c))
117                 break;
118             if (c == '#')
119             {
120                 ungetc(c, fp);
121                 break;
122             }
123         }
124         *bp = 0;
125         if (buffer[0] == '@')
126             read_arguments_file(buffer + 1);
127         else
128             arguments.push_back(std::string(buffer, bp - buffer));
129     }
130     fclose(fp);
131 }
132 
133 
~arglex()134 srecord::arglex::~arglex()
135 {
136 }
137 
138 
139 void
table_set(const table_ty * tp)140 srecord::arglex::table_set(const table_ty *tp)
141 {
142     tables.push_back(tp);
143 }
144 
145 
146 static const char *partial;
147 
148 
149 bool
compare(const char * formal,const char * actual)150 srecord::arglex::compare(const char *formal, const char *actual)
151 {
152     for (;;)
153     {
154         unsigned char ac = *actual++;
155         if (isupper(ac))
156             ac = tolower(ac);
157         unsigned char fc = *formal++;
158         switch (fc)
159         {
160         case 0:
161             return !ac;
162 
163         case '_':
164             if (ac == '-')
165                 break;
166             // fall through...
167 
168         case 'a': case 'b': case 'c': case 'd': case 'e':
169         case 'f': case 'g': case 'h': case 'i': case 'j':
170         case 'k': case 'l': case 'm': case 'n': case 'o':
171         case 'p': case 'q': case 'r': case 's': case 't':
172         case 'u': case 'v': case 'w': case 'x': case 'y':
173         case 'z':
174             //
175             // optional characters
176             //
177             if (ac == fc && compare(formal, actual))
178                 return true;
179 
180             //
181             // skip forward to next
182             // mandatory character, or after '_'
183             //
184             while (islower(*formal))
185                 ++formal;
186             if (*formal == '_')
187             {
188                 ++formal;
189                 if (ac == '_' || ac == '-')
190                     ++actual;
191             }
192             --actual;
193             break;
194 
195         case '*':
196             //
197             // This is incomplete, it should really
198             // check for a match match of the stuff after
199             // the '*', too, a la glob.
200             //
201             if (!ac)
202                 return false;
203             partial = actual - 1;
204             return true;
205 
206         case '\\':
207             if (actual[-1] != *formal++)
208                 return false;
209             break;
210 
211         case 'A': case 'B': case 'C': case 'D': case 'E':
212         case 'F': case 'G': case 'H': case 'I': case 'J':
213         case 'K': case 'L': case 'M': case 'N': case 'O':
214         case 'P': case 'Q': case 'R': case 'S': case 'T':
215         case 'U': case 'V': case 'W': case 'X': case 'Y':
216         case 'Z':
217             fc = tolower(fc);
218             // fall through...
219 
220         default:
221             //
222             // mandatory characters
223             //
224             if (fc != ac)
225                 return false;
226             break;
227         }
228     }
229 }
230 
231 
232 //
233 // NAME
234 //      is_a_number
235 //
236 // SYNOPSIS
237 //      int is_a_number(char *s);
238 //
239 // DESCRIPTION
240 //      The is_a_number function is used to determine if the
241 //      argument is a number.
242 //
243 //      The value is placed in arglex_value.alv_number as
244 //      a side effect.
245 //
246 //      Negative and positive signs are accepted.
247 //      The C conventions for decimal, octal and hexadecimal are understood.
248 //
249 //      There may be no white space anywhere in the string,
250 //      and the string must end after the last digit.
251 //      Trailing garbage will be interpreted to mean it is not a string.
252 //
253 // ARGUMENTS
254 //      s       - string to be tested and evaluated
255 //
256 // RETURNS
257 //      int;    zero if not a number,
258 //              non-zero if is a number.
259 //
260 
261 static int
is_a_number(const char * s,long & n)262 is_a_number(const char *s, long &n)
263 {
264     int         sign;
265 
266     n = 0;
267     switch (*s)
268     {
269     case '-':
270         ++s;
271         sign = -1;
272         break;
273 
274     case '+':
275         ++s;
276         sign = 1;
277         break;
278 
279     default:
280         sign = 1;
281         break;
282     }
283     switch (*s)
284     {
285     case '0':
286         if ((s[1] == 'x' || s[1] == 'X') && s[2])
287         {
288             s += 2;
289             for (;;)
290             {
291                 switch (*s)
292                 {
293                 case '0': case '1': case '2': case '3':
294                 case '4': case '5': case '6': case '7':
295                 case '8': case '9':
296                     n = n * 16 + *s++ - '0';
297                     continue;
298 
299                 case 'A': case 'B': case 'C':
300                 case 'D': case 'E': case 'F':
301                     n = n * 16 + *s++ - 'A' + 10;
302                     continue;
303 
304                 case 'a': case 'b': case 'c':
305                 case 'd': case 'e': case 'f':
306                     n = n * 16 + *s++ - 'a' + 10;
307                     continue;
308                 }
309                 break;
310             }
311         }
312         else
313         {
314             for (;;)
315             {
316                 switch (*s)
317                 {
318                 case '0': case '1': case '2': case '3':
319                 case '4': case '5': case '6': case '7':
320                     n = n * 8 + *s++ - '0';
321                     continue;
322                 }
323                 break;
324             }
325         }
326         break;
327 
328     case '1': case '2': case '3': case '4':
329     case '5': case '6': case '7': case '8': case '9':
330         for (;;)
331         {
332             switch (*s)
333             {
334             case '0': case '1': case '2': case '3':
335             case '4': case '5': case '6': case '7':
336             case '8': case '9':
337                 n = n * 10 + *s++ - '0';
338                 continue;
339             }
340             break;
341         }
342         break;
343 
344 default:
345         return 0;
346     }
347     if (*s)
348         return 0;
349     n *= sign;
350     return 1;
351 }
352 
353 
354 static bool
starts_with(const std::string & haystack,const std::string & needle)355 starts_with(const std::string &haystack, const std::string &needle)
356 {
357     return
358         (
359             haystack.size() >= needle.size()
360         &&
361             0 == memcmp(haystack.c_str(), needle.c_str(), needle.size())
362         );
363 }
364 
365 
366 static bool
ends_with(const std::string & haystack,const std::string & needle)367 ends_with(const std::string &haystack, const std::string &needle)
368 {
369     return
370         (
371             haystack.size() >= needle.size()
372         &&
373             (
374                 0
375             ==
376                 memcmp
377                 (
378                     haystack.c_str() + haystack.size() - needle.size(),
379                     needle.c_str(),
380                     needle.size()
381                 )
382             )
383         );
384 }
385 
386 
387 static void
deprecated_warning(const char * deprecated_name,const char * preferred_name)388 deprecated_warning(const char *deprecated_name, const char *preferred_name)
389 {
390     srecord::quit_default.warning
391     (
392         "option \"%s\" is deprecated, please use \"%s\" instead",
393         deprecated_name,
394         preferred_name
395     );
396 }
397 
398 
399 //
400 // NAME
401 //      arglex
402 //
403 // SYNOPSIS
404 //      int arglex::token_next(void);
405 //
406 // DESCRIPTION
407 //      The arglex function is used to perfom lexical analysis
408 //      on the command line arguments.
409 //
410 //      Unrecognised options are returned as arglex_token_option
411 //      for anything starting with a '-', or
412 //      arglex_token_string otherwise.
413 //
414 // RETURNS
415 //      The next token in the token stream.
416 //      When the end is reached, arglex_token_eoln is returned forever.
417 //
418 // CAVEAT
419 //      Must call arglex_init befor this function is called.
420 //
421 
422 int
token_next(void)423 srecord::arglex::token_next(void)
424 {
425     const table_ty  *tp;
426     const table_ty  *hit[20];
427     int             nhit;
428 
429     std::string arg;
430     if (!pushback.empty())
431     {
432         //
433         // the second half of a "-foo=bar" style argument.
434         //
435         arg = pushback.back();
436         pushback.pop_back();
437     }
438     else
439     {
440         if (arguments.empty())
441         {
442             value_string_ = "";
443             token = token_eoln;
444             return token;
445         }
446         arg = arguments.front();
447         arguments.pop_front();
448 
449         //
450         // See if it looks like a GNU "-foo=bar" option.
451         // Split it at the '=' to make it something the
452         // rest of the code understands.
453         //
454         if (arg[0] == '-' && arg[1] != '=')
455         {
456             const char *eqp = strchr(arg.c_str(), '=');
457             if (eqp)
458             {
459                 pushback.push_back(eqp + 1);
460                 arg = std::string(arg.c_str(), eqp - arg.c_str());
461             }
462         }
463 
464         //
465         // Turn the GNU-style leading "--"
466         // into "-" if necessary.
467         //
468         if
469         (
470             arg.size() > 2
471         &&
472             arg[0] == '-'
473         &&
474             arg[1] == '-'
475         &&
476             !is_a_number(arg.c_str() + 1, value_number_)
477         )
478             arg = std::string(arg.c_str() + 1);
479     }
480     value_string_ = arg;
481 
482     //
483     // see if it is a number
484     //
485     if (is_a_number(arg.c_str(), value_number_))
486     {
487         token = arglex::token_number;
488         return token;
489     }
490 
491     //
492     // scan the tables to see what it matches
493     //
494     nhit = 0;
495     partial = 0;
496     for
497     (
498         table_ptr_vec_t::iterator it = tables.begin();
499         it != tables.end();
500         ++it
501     )
502     {
503         for (tp = *it; tp->name; ++tp)
504         {
505             if (compare(tp->name, arg.c_str()))
506                 hit[nhit++] = tp;
507 
508             // big endian deprecated variants
509             assert(!starts_with(tp->name, "-Big_Endian_"));
510             if (ends_with(tp->name, "_Big_Endian"))
511             {
512                 std::string name2 =
513                     "-Big_Endian_" +
514                     std::string(tp->name + 1, strlen(tp->name) - 12);
515                 if (compare(name2.c_str(), arg.c_str()))
516                 {
517                     hit[nhit++] = tp;
518                     deprecated_warning(name2.c_str(), tp->name);
519                 }
520             }
521 
522             // little endian deprecated variants
523             assert(!starts_with(tp->name, "-Little_Endian_"));
524             if (ends_with(tp->name, "_Little_Endian"))
525             {
526                 std::string name3 =
527                     "-Little_Endian_" +
528                     std::string(tp->name + 1, strlen(tp->name) - 15);
529                 if (compare(name3.c_str(), arg.c_str()))
530                 {
531                     hit[nhit++] = tp;
532                     deprecated_warning(name3.c_str(), tp->name);
533                 }
534             }
535         }
536     }
537 
538     //
539     // deal with unknown or ambiguous options
540     //
541     switch (nhit)
542     {
543     case 0:
544         //
545         // not found in the tables
546         //
547         if (value_string_[0] == '-')
548             token = arglex::token_option;
549         else
550             token = arglex::token_string;
551         break;
552 
553     default:
554         //
555         // if all the hits are the same, it isn't ambiguous
556         //
557         {
558             bool all_same = true;
559             std::string possibilities = hit[0]->name;
560             for (int k = 1; k < nhit; ++k)
561             {
562                 if (hit[0]->token != hit[k]->token)
563                     all_same = false;
564                 possibilities += ", ";
565                 possibilities += hit[k]->name;
566             }
567             if (!all_same)
568             {
569                 fatal_error
570                 (
571                     "option \"%s\" is ambiguous, did you mean one of: %s?",
572                     value_string_.c_str(),
573                     possibilities.c_str()
574                 );
575                 // NOTREACHED
576             }
577         }
578         // fall through...
579 
580     case 1:
581         if (partial)
582         {
583             pushback.push_back(partial);
584             partial = 0;
585         }
586         value_string_ = hit[0]->name;
587         token = hit[0]->token;
588         check_deprecated(arg);
589         break;
590     }
591     return token;
592 }
593 
594 
595 void
deprecated_option(const std::string & old_fashioned)596 srecord::arglex::deprecated_option(const std::string &old_fashioned)
597 {
598     deprecated_options.push_back(old_fashioned);
599 }
600 
601 
602 void
check_deprecated(const std::string & actual) const603 srecord::arglex::check_deprecated(const std::string &actual)
604     const
605 {
606     for
607     (
608         deprecated_options_t::const_iterator it = deprecated_options.begin();
609         it != deprecated_options.end();
610         ++it
611     )
612     {
613         std::string formal = *it;
614         if (compare(formal.c_str(), actual.c_str()))
615         {
616             deprecated_warning(formal.c_str(), token_name(token));
617         }
618     }
619 }
620 
621 
622 const char *
token_name(int n) const623 srecord::arglex::token_name(int n)
624     const
625 {
626     switch (n)
627     {
628     case token_eoln:
629         return "end of command line";
630 
631     case token_number:
632         return "number";
633 
634     case token_option:
635         return "option";
636 
637     case token_stdio:
638         return "standard input or output";
639 
640     case token_string:
641         return "string";
642 
643     default:
644         break;
645     }
646     for
647     (
648         table_ptr_vec_t::const_iterator it = tables.begin();
649         it != tables.end();
650         ++it
651     )
652     {
653         for (const table_ty *tp = *it; tp->name; ++tp)
654         {
655             if (tp->token == n)
656                 return tp->name;
657         }
658     }
659     return "unknown command line token";
660 }
661 
662 
663 void
help(const char * name) const664 srecord::arglex::help(const char *name)
665     const
666 {
667     if (!name)
668         name = progname_get();
669     const char *cmd[3] = { "man", name, 0 };
670     execvp(cmd[0], (char *const *)cmd);
671     std::cerr << cmd[0] << ": " << strerror(errno) << std::endl;
672     exit(1);
673 }
674 
675 
676 void
version(void) const677 srecord::arglex::version(void)
678     const
679 {
680     print_version();
681     exit(0);
682 }
683 
684 
685 void
license(void) const686 srecord::arglex::license(void)
687     const
688 {
689     help("srecord::license");
690 }
691 
692 
693 void
bad_argument(void) const694 srecord::arglex::bad_argument(void)
695     const
696 {
697     switch (token_cur())
698     {
699     case token_string:
700         std::cerr << "misplaced file name (\"" << value_string()
701             << "\") on command line" << std::endl;
702         break;
703 
704     case token_number:
705         std::cerr << "misplaced number (" << value_string()
706             << ") on command line" << std::endl;
707         break;
708 
709     case token_option:
710         std::cerr << "unknown \"" << value_string() << "\" option" << std::endl;
711         break;
712 
713     case token_eoln:
714         std::cerr << "command line too short" << std::endl;
715         break;
716 
717     default:
718         std::cerr << "misplaced \"" << value_string() << "\" option"
719             << std::endl;
720         break;
721     }
722     usage();
723     // NOTREACHED
724 }
725 
726 
727 int
token_first(void)728 srecord::arglex::token_first(void)
729 {
730 #if 1
731     // We probably don't need to do this all the time.
732     // How do we distinguish Peter's build from a package build?
733     test_ambiguous();
734 #endif
735 
736     switch (token_next())
737     {
738     default:
739         return token_cur();
740 
741     case token_help:
742         if (token_next() != token_eoln)
743             bad_argument();
744         help();
745         break;
746 
747     case token_version:
748         if (token_next() != token_eoln)
749             bad_argument();
750         version();
751         break;
752 
753     case token_license:
754         if (token_next() != token_eoln)
755             bad_argument();
756         license();
757         break;
758     }
759     exit(0);
760 }
761 
762 
763 void
usage_tail_set(const char * s)764 srecord::arglex::usage_tail_set(const char *s)
765 {
766     usage_tail_ = s;
767 }
768 
769 
770 const char *
usage_tail_get(void) const771 srecord::arglex::usage_tail_get(void)
772     const
773 {
774     if (!usage_tail_)
775         usage_tail_ = "<filename>...";
776     return usage_tail_;
777 }
778 
779 
780 void
usage(void) const781 srecord::arglex::usage(void)
782     const
783 {
784     std::cerr << "Usage: " << progname_get() << " [ <option>... ] "
785         << usage_tail_get() << std::endl;
786     std::cerr << "       " << progname_get() << " -Help" << std::endl;
787     std::cerr << "       " << progname_get() << " -VERSion" << std::endl;
788     std::cerr << "       " << progname_get() << " -LICense" << std::endl;
789     exit(1);
790     // NOTREACHED
791 }
792 
793 
794 void
default_command_line_processing(void)795 srecord::arglex::default_command_line_processing(void)
796 {
797     bad_argument();
798 }
799 
800 
801 void
fatal_error(const char * fmt,...)802 srecord::arglex::fatal_error(const char *fmt, ...)
803 {
804     va_list ap;
805     va_start(ap, fmt);
806     quit_default.fatal_error_v(fmt, ap);
807     // NOTREACHED
808     va_end(ap);
809 }
810 
811 // vim: set ts=8 sw=4 et :
812