The tcc options behavior is non trivial, and it's also susceptible to ambiguities - which do exist (but currently are not painful). - Add a script 'optclash' which detects clashes/ambiguities. - Document the parsing behaviour and current clashes/resolutions as comments in libtcc.c .
Unified · Split
| @@ -1571,6 +1571,30 @@ enum { · 1570 unmodified lines | ||
| 1571 | 1571 | #define TCC_OPTION_HAS_ARG 0x0001 |
| 1572 | 1572 | #define TCC_OPTION_NOSEP 0x0002 /* cannot have space before option and arg */ |
| 1573 | 1573 | |
| 1574 | +/* | |
| 1575 | + * in tcc_options, if opt-string A is a prefix of opt-string B, | |
| 1576 | + * it's un-ambiguous if and only if option A is without TCC_OPTION_HAS_ARG. | |
| 1577 | + * otherwise (A with HAS_ARG), if, for instance, A is FOO and B is FOOBAR, | |
| 1578 | + * then "-FOOBAR" is either A with arg BAR, or B (-FOOBARX too, if B HAS_ARG). | |
| 1579 | + * | |
| 1580 | + * tcc_parse_args searches tcc_options in order, so if ambiguous: | |
| 1581 | + * - if the shorter (A) is earlier: the longer (B) is completely unreachable. | |
| 1582 | + * - else B wins, and A can't be used with adjacent arg if it also matches B. | |
| 1583 | + * | |
| 1584 | + * there are few clashes currently, and the longer is always earlier/reachable. | |
| 1585 | + * when it's ambiguous, shorter-concat-arg is not useful currently. | |
| 1586 | + * the sh(1) script 'optclash' can identifiy clashes (tcc root dir, try "-h"). | |
| 1587 | + * at the time of writing, running './optclash' prints this: | |
| 1588 | + | |
| 1589 | + -Wl,... (1642) overrides -W... (1644) | |
| 1590 | + -Wp,... (1643) overrides -W... (1644) | |
| 1591 | + -dumpmachine (1630) overrides -d... (1632) | |
| 1592 | + -dumpversion (1631) overrides -d... (1632) | |
| 1593 | + -dynamiclib (1623) overrides -d... (1632) | |
| 1594 | + -flat_namespace (1624) overrides -f... (1650) | |
| 1595 | + -mfloat-abi... (1647) overrides -m... (1649) | |
| 1596 | + | |
| 1597 | + */ | |
| 1574 | 1598 | static const TCCOption tcc_options[] = { |
| 1575 | 1599 | { "h", TCC_OPTION_HELP, 0 }, |
| 1576 | 1600 | { "-help", TCC_OPTION_HELP, 0 }, |
| @@ -1808,6 +1832,27 @@ static void args_parser_add_file(TCCState *s, const char* filename, int filetype · 231 unmodified lines | ||
| 1808 | 1832 | ++s->nb_libraries; |
| 1809 | 1833 | } |
| 1810 | 1834 | |
| 1835 | +/* parsing is between getopt(3) and getopt_long(3), and permuting-like: | |
| 1836 | + * - an option is 1 or more chars. | |
| 1837 | + * - at most 1 option per arg in argv. | |
| 1838 | + * - an option in argv is "-OPT[...]" (few are --OPT, if OPT is "-..."). | |
| 1839 | + * - optarg is next arg, or adjacent non-empty (no '='. -std=.. is arg "=.."). | |
| 1840 | + * - supports also adjacent-only optarg (typically optional). | |
| 1841 | + * - supports mixed options and operands ("--" is ignored, except with -run). | |
| 1842 | + * - -OPT[...] can be ambiguous, which is resolved using tcc_options's order. | |
| 1843 | + * (see tcc_options for details) | |
| 1844 | + * | |
| 1845 | + * specifically, per arg of argv, in order: | |
| 1846 | + * - if arg begins with '@' and is not exactly "@": process as @listfile. | |
| 1847 | + * - elif arg is exactly "-" or doesn't begin with '-': process as input file. | |
| 1848 | + * - if -run... is already set: also stop, arg... become argv of run_main. | |
| 1849 | + * - elif arg is "--": | |
| 1850 | + * - if -run... is already set: stop, arg... become argv of run_main. | |
| 1851 | + * - else ignore it. | |
| 1852 | + * - else ("-STRING") try to apply it as option, maybe with next (opt)arg. | |
| 1853 | + * | |
| 1854 | + * after all args, if -run... but no "stop": run_main gets our argv (tcc ...) | |
| 1855 | + */ | |
| 1811 | 1856 | /* using * to argc/argv to let "tcc -ar" benefit from @listfile expansion */ |
| 1812 | 1857 | PUB_FUNC int tcc_parse_args(TCCState *s, int *pargc, char ***pargv) |
| 1813 | 1858 | { |
| @@ -0,0 +1,38 @@ | ||
| 1 | +#!/bin/sh | |
| 2 | + | |
| 3 | +export LC_ALL=C | |
| 4 | + | |
| 5 | +defname=libtcc.c | |
| 6 | + | |
| 7 | +# $1 is the line number, $2... is the actual source line | |
| 8 | +extract_opts() { awk '/tcc_options\[\]/ {x=1}; x; x && $2~/^}/ {exit}'; } | |
| 9 | + | |
| 10 | +case $1 in -h|--help) | |
| 11 | + echo "Usage: $0 [INFILE]" | |
| 12 | + echo "Detect tcc_options[] clashes in $defname-like INFILE." | |
| 13 | + echo "If INFILE is missing, use $defname at the same dir as this script." | |
| 14 | + echo "Clashes are reported as longer-overrides, or longer-unreachable." | |
| 15 | + exit | |
| 16 | +esac | |
| 17 | + | |
| 18 | +f=${1-$(dirname "$0")/$defname} | |
| 19 | +[ -r "$f" ] || { >&2 echo "$0: can't read -- $f"; exit 1; } | |
| 20 | + | |
| 21 | +nl -b a <"$f" | extract_opts | tr \" ' ' | awk '$2=="{"' | sort -b -k 3 | | |
| 22 | +# "<line-num> { <unquoted-opt> <rest-of-line>" sorted-up by opt | |
| 23 | +# unavoidable O(N^2). the sort simplifies the awk code - only test prior opts. | |
| 24 | +awk ' | |
| 25 | + { | |
| 26 | + n=$1; opt=$3; h=/HAS_ARG/ | |
| 27 | + for (pn in prevs) { # pn: line-num | |
| 28 | + po = prevs[pn] # po: opt-with-has-arg | |
| 29 | + if (index(opt,po) == 1) { | |
| 30 | + clash=1 | |
| 31 | + printf("-%s%s (%d) %s -%s... (%s)\n", opt,h?"...":"",n, | |
| 32 | + n>pn? "is not reachable! by":"overrides", po,pn) | |
| 33 | + } | |
| 34 | + } | |
| 35 | + } | |
| 36 | + h {prevs[n] = opt} | |
| 37 | + END {if (clash) exit 1; print "no clashes"} | |
| 38 | +' | |