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