-
Notifications
You must be signed in to change notification settings - Fork 13
/
c99sh
executable file
·217 lines (195 loc) · 8.43 KB
/
c99sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#!/usr/bin/env bash
# c99sh: A shebang-friendly script for "interpreting" single C99 files
# from https://github.com/rhysu/c99sh. Subject to the Simplified BSD License.
# Implementation deliberately jumps through hoops to reduce process spawning.
set -eu
shopt -s extglob
name=${0##*/}
# Maps requested pkg-config packages to the origin of the request
declare -A pkg=()
stderr_pkgs() {
for n in "${!pkg[@]}"; do
echo "info: ${pkg[$n]} requested package $n" 1>&2
done
}
# Process c99sh flags (not arguments to the interpreted script)
help="\
Usage: ${name} [OPTION]... [--] PROGRAM [PROGRAMOPTION]...
or: ${name} [OPTION]... [--] - [PROGRAMOPTION]...
or: ${name} [OPTION]... [--]
Compile ${name:0:3} PROGRAM, or standard input, and run it supplying [PROGRAMOPTION]...
Options:
-e LINE Prepends LINE to any input; often used in conjunction with -ms
-h Display this help message
-l LIB Link to the library LIB
-m Wrap input in canonical main(argc, argv) declaration
-p PKG Make PKG headers and libraries available to PROGRAM via pkg-config(1)
-r RC Load compilation settings from RC suppressing normal rcfile search
-s Include all standard headers defined by ISO/IEC 9899:1999
-t STMT Append a main(argc, argv) implementation running statement STMT
-v Increase verbosity; may be supplied multiple times
-x EXE Save a successfully compiled executable as EXE instead of running it
-F OPT Add '-OPT' to \$CFLAGS when using \$CFLAGS during compilation
-L OPT Add '-OPT' to \$LDFLAGS when using \$LDFLAGS during linking
An rcfile '${name}rc' controls compilation if present in the same directory as
PROGRAM, or if present in the current working directory when processing standard
input. Otherwise, if it exists, the file ~/.${name}rc controls compilation.
If compilation is successful, the exit status is that of PROGRAM.
"
declare -i e=0 m=0 s=0 v=0 l=0
declare -A E=() L=()
while getopts e:hl:mp:r:st:vx:F:L: flag; do
case $flag in
e) E[$e]=$OPTARG; ((e+=1)) ;; # To process later; notice 'E' not 'e'
h) exec echo -ne "$help" ;; # Stops processing due to exec
l) L[$l]=$OPTARG; ((l+=1)) ;; # Libraries to be processed later
m) m=1 ;; # To process later
p) pkg[$OPTARG]="flag -$flag" ;; # Additively record requested package
r) r=$OPTARG ;; # Record requested rcfile, last one wins
s) s=1 ;; # To process later
t) T=$OPTARG; ;; # To process later; notice 'T' not 't'
v) ((v+=1)) ;; # Try -v, -vv, etc. to increase verbosity
x) X=$OPTARG ;; # To process later; notice 'X' not 'x'
F) CFLAGS="${CFLAGS:+${CFLAGS} }-$OPTARG" ;; # Space-separated append
L) LDFLAGS="${LDFLAGS:+${LDFLAGS} }-$OPTARG" ;; # Space-separated append
?) exit; ;;
esac
done
shift $((OPTIND-1))
# Providing -vvv is maximally verbose
[ $v -ge 3 ] && set -x
# Process standard input on absent or '-' filename otherwise snarf $1
f="<stdin>"; d="."
if [ $# -gt 0 ]
then if [ "$1" != "-" ]
then f="$1"
d=$(dirname "$1")
exec 0< "$1"
fi
shift 1
fi
c=$(mktemp "${TMPDIR-/tmp}/${name:0:3}sh.XXXXXX") # Mangled source for compiling
x=$(mktemp "${TMPDIR-/tmp}/${name:0:3}sh.XXXXXX") # Compiled binary to be run
trap 'rm -f "$c" "$x"' EXIT # Automatic cleanup on exit
# If requested, include all standard C99 headers in either C or C++ fashion
if [ $s -gt 0 ]
then echo "#line $LINENO \"$0\"" >>"$c"
head=( assert complex ctype errno fenv float inttypes iso646
limits locale math setjmp signal stdarg stdbool stddef
stdint stdio stdlib string tgmath time wchar wctype )
case "${name:0:3}" in
c99|C99) for h in "${head[@]}"; do echo "#include <${h}.h>" >>"$c"; done ;;
c11|C11) for h in "${head[@]}"; do echo "#include <${h}.h>" >>"$c"; done ;;
cxx|CXX) for h in "${head[@]}"; do echo "#include <c${h}>" >>"$c"; done ;;
esac
fi
# Assists in reporting formatted errors to stderr followed by exiting
fatal() { status=$1; shift; printf "$@" 1>&2; echo 1>&2; exit $status; }
# Logic for accumulating details from some named rcfile
declare -a flags=(${CFLAGS-} "-I$d") # CPPFLAGS, CFLAGS
declare -a logic=("$c" ${LDFLAGS-}) # Sources, LDFLAGS, LIBS
process_rcfile() {
local i=0
while IFS=$' \t\n' read -r o; do
echo "#line $((i+=1)) \"$1\" // $o" >>"$c" # Record line in munged source
if [ -z "$o" ] # Skip empty lines
then :
elif [[ $o =~ ^// ]] # Single line comments: //
then echo "$o" >>"$c"
elif [[ $o =~ ^# ]] # C preprocessor directives
then echo "$o" >>"$c"
elif [[ $o =~ ^-[lLR] ]] # Linker flags
then logic+=($o)
elif [[ $o =~ ^- ]] # Compiler flags
then flags+=($o)
elif [[ $o =~ ^using|^namespace ]] # Namespace directives
then echo "$o;" >>"$c"
elif [[ $o =~ ^pkg-config[[:space:]]+ ]] # Declaration for pkg-config..
then local p=${o##pkg-config+([[:space:]])} # ..requires lengthier parsing
if [[ $p =~ ^--cflags ]]
then fatal $LINENO "$1:$i error: extraneous '--cflags'\n\t$o\n\t^"
elif [[ $p =~ ^--libs ]]
then fatal $LINENO "$1:$i error: extraneous '--libs'\n\t$o\n\t^"
elif [[ $p =~ ^[-+/._[:space:][:alnum:]]+$ ]]
then for q in $p; do pkg[$q]="$1:$i"; done
else fatal $LINENO "$1:$i error: invalid package name(s)\n\t$o\n\t^"
fi
elif [ -f "$o" ] # Linker target
then logic+=($o)
else fatal $LINENO "$1:$i error: non-existent or unknown option\n\t$o\n\t^"
fi
done < "$1"
}
# Process only the first rcfile; permits directory-specific settings
if [ -n "${r-}" ]
then process_rcfile "$r"
elif [ -f "$d/${name}rc" ]
then process_rcfile "$d/${name}rc"
elif [ -f "$HOME/.${name}rc" ]
then process_rcfile "$HOME/.${name}rc"
fi
# Combine all package requests to permit invoking pkg-config at most twice
# Also permits smart merging of results and package conflict detection
# Causes all pkg-config --libs to appear after linker targets. Ok?
if [ ${#pkg[@]} -gt 0 ]
then flags+=($(pkg-config --cflags ${!pkg[@]})) || (echo 1>&2; stderr_pkgs; \
fatal $LINENO "error: failure obtaining --cflags from pkg-config")
logic+=($(pkg-config --libs ${!pkg[@]})) || (echo 1>&2; stderr_pkgs; \
fatal $LINENO "error: failure obtaining --libs from pkg-config")
fi
# Helpers to emit a main declaration silencing unused argc, argv warnings
emit_main_open() {
echo "#line $LINENO \"$0\"" >>"$c"
echo "int main(int argc, char *argv[]) {" >>"$c"
echo "(void) argc; (void) argv;" >>"$c"
}
emit_main_close() {
echo "#line $LINENO \"$0\"" >>"$c"
echo "} /*main*/" >>"$c"
}
# If requested, open main function about entire input
[[ $m -gt 0 ]] && emit_main_open
# If requested, emit one or more statements possibly inside main
for ((i = 0; i < e; ++i)); do
echo "${E[$i]}" >> "$c"
done
# Prepare source from standard input for "interpretation" by the compiler.
# Possibly swap shebang on first input line with a preprocessor line pragma so
# (a) the source can be compiled without an unknown pragma warning, and
# (b) errors and warnings within the file show usable line information
echo "#line 1 \"$f\"" >>"$c"
sed "1s|^#!.*\$|#line 2 \"$f\"|" >>"$c"
# If requested, close main function about entire input
[[ $m -gt 0 ]] && emit_main_close
# If requested, append a main function containing the given statement
if [ -n "${T-}" ]
then emit_main_open
echo "$T;" >> "$c"
emit_main_close
fi
# Add libraries from command line options
for ((i = 0; i < l; ++i)); do
logic+=("-l${L[$i]}")
done
# Compile the generated source with appropriate verbosity
[ $v -ge 2 ] && cat "$c" 1>&2
[ $v -ge 1 ] && set -x
case "${name:0:3}" in
c11|C11)
"${CC-cc}" -std=gnu11 -o "$x" "${flags[@]}" -x c "${logic[@]}" 1>&2
;;
c99|C99)
"${CC-cc}" -std=gnu99 -o "$x" "${flags[@]}" -x c "${logic[@]}" 1>&2
;;
cxx|CXX)
"${CXX-c++}" -std=gnu++14 -o "$x" "${flags[@]}" -x c++ "${logic[@]}" 1>&2
;;
*)
fatal $LINENO "error: unrecognized language prefix - ${name:0:3}"
;;
esac
# Either save or execute the binary with any remaining arguments
if [ -n "${X-}" ]
then cp "$x" "$X"
else "$x" "$@"
fi