MKlib_gen.sh revision 358203
1#!/bin/sh
2#
3# MKlib_gen.sh -- generate sources from curses.h macro definitions
4#
5# ($Id: MKlib_gen.sh,v 1.50 2015/08/07 00:48:24 tom Exp $)
6#
7##############################################################################
8# Copyright (c) 1998-2014,2015 Free Software Foundation, Inc.                #
9#                                                                            #
10# Permission is hereby granted, free of charge, to any person obtaining a    #
11# copy of this software and associated documentation files (the "Software"), #
12# to deal in the Software without restriction, including without limitation  #
13# the rights to use, copy, modify, merge, publish, distribute, distribute    #
14# with modifications, sublicense, and/or sell copies of the Software, and to #
15# permit persons to whom the Software is furnished to do so, subject to the  #
16# following conditions:                                                      #
17#                                                                            #
18# The above copyright notice and this permission notice shall be included in #
19# all copies or substantial portions of the Software.                        #
20#                                                                            #
21# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #
22# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   #
23# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    #
24# THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER      #
25# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    #
26# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER        #
27# DEALINGS IN THE SOFTWARE.                                                  #
28#                                                                            #
29# Except as contained in this notice, the name(s) of the above copyright     #
30# holders shall not be used in advertising or otherwise to promote the sale, #
31# use or other dealings in this Software without prior written               #
32# authorization.                                                             #
33##############################################################################
34#
35# The XSI Curses standard requires all curses entry points to exist as
36# functions, even though many definitions would normally be shadowed
37# by macros.  Rather than hand-hack all that code, we actually
38# generate functions from the macros.
39#
40# This script accepts a file of prototypes on standard input.  It discards
41# any that don't have a `generated' comment attached. It then parses each
42# prototype (relying on the fact that none of the macros take function
43# pointer or array arguments) and generates C source from it.
44#
45# Here is what the pipeline stages are doing:
46#
47# 1. sed: extract prototypes of generated functions
48# 2. sed: decorate prototypes with generated arguments a1. a2,...z
49# 3. awk: generate the calls with args matching the formals
50# 4. sed: prefix function names in prototypes so the preprocessor won't expand
51#         them.
52# 5. cpp: macro-expand the file so the macro calls turn into C calls
53# 6. awk: strip the expansion junk off the front and add the new header
54# 7. sed: squeeze spaces, strip off gen_ prefix.
55#
56
57# keep the editing independent of locale:
58if test "${LANGUAGE+set}"    = set; then LANGUAGE=C;    export LANGUAGE;    fi
59if test "${LANG+set}"        = set; then LANG=C;        export LANG;        fi
60if test "${LC_ALL+set}"      = set; then LC_ALL=C;      export LC_ALL;      fi
61if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi
62if test "${LC_CTYPE+set}"    = set; then LC_CTYPE=C;    export LC_CTYPE;    fi
63if test "${LC_COLLATE+set}"  = set; then LC_COLLATE=C;  export LC_COLLATE;  fi
64
65preprocessor="$1 -DNCURSES_INTERNALS -I../include"
66AWK="$2"
67USE="$3"
68
69# A patch discussed here:
70#	https://gcc.gnu.org/ml/gcc-patches/2014-06/msg02185.html
71# introduces spurious #line markers into the preprocessor output.  The result
72# appears in gcc 5.0 and (with modification) in 5.1, making it necessary to
73# determine if we are using gcc, and if so, what version because the proposed
74# solution uses a nonstandard option.
75PRG=`echo "$1" | $AWK '{ sub(/^[[:space:]]*/,""); sub(/[[:space:]].*$/, ""); print; }' || exit 0`
76FSF=`("$PRG" --version 2>/dev/null || exit 0) | fgrep "Free Software Foundation" | head -n 1`
77ALL=`"$PRG" -dumpversion 2>/dev/null || exit 0`
78ONE=`echo "$ALL" | sed -e 's/\..*$//'`
79if test -n "$FSF" && test -n "$ALL" && test -n "$ONE" ; then
80	if test $ONE -ge 5 ; then
81		echo ".. adding -P option to work around $PRG $ALL" >&2
82		preprocessor="$preprocessor -P"
83	fi
84fi
85
86PID=$$
87ED1=sed1_${PID}.sed
88ED2=sed2_${PID}.sed
89ED3=sed3_${PID}.sed
90ED4=sed4_${PID}.sed
91AW1=awk1_${PID}.awk
92AW2=awk2_${PID}.awk
93TMP=gen__${PID}.c
94trap "rm -f $ED1 $ED2 $ED3 $ED4 $AW1 $AW2 $TMP" 0 1 2 5 15
95
96ALL=$USE
97if test "$USE" = implemented ; then
98	CALL="call_"
99	cat >$ED1 <<EOF1
100/^extern.*implemented/{
101	h
102	s/NCURSES_SP_NAME(\([^)]*\))/NCURSES_SP_NAME___\1/
103	h
104	s/^.*implemented:\([^ 	*]*\).*/P_POUNDCif_USE_\1_SUPPORT/p
105	g
106	s/^extern \([^;]*\);.*/\1/p
107	g
108	s/^.*implemented:\([^ 	*]*\).*/P_POUNDCendif/p
109}
110/^extern.*generated/{
111	h
112	s/^.*generated:\([^ 	*]*\).*/P_POUNDCif_USE_\1_SUPPORT/p
113	g
114	s/^extern \([^;]*\);.*/\1/p
115	g
116	s/^.*generated:\([^ 	*]*\).*/P_POUNDCendif/p
117}
118EOF1
119else
120	CALL=""
121	cat >$ED1 <<EOF1
122/^extern.*${ALL}/{
123	h
124	s/^.*${ALL}:\([^ 	*]*\).*/P_POUNDCif_USE_\1_SUPPORT/p
125	g
126	s/^extern \([^;]*\);.*/\1/p
127	g
128	s/^.*${ALL}:\([^ 	*]*\).*/P_POUNDCendif/p
129}
130EOF1
131fi
132
133cat >$ED2 <<EOF2
134/^P_/b nc
135/(void)/b nc
136	s/,/ a1% /
137	s/,/ a2% /
138	s/,/ a3% /
139	s/,/ a4% /
140	s/,/ a5% /
141	s/,/ a6% /
142	s/,/ a7% /
143	s/,/ a8% /
144	s/,/ a9% /
145	s/,/ a10% /
146	s/,/ a11% /
147	s/,/ a12% /
148	s/,/ a13% /
149	s/,/ a14% /
150	s/,/ a15% /
151	s/*/ * /g
152	s/%/ , /g
153	s/)/ z)/
154	s/\.\.\. z)/...)/
155:nc
156	s/(/ ( /
157	s/)/ )/
158EOF2
159
160cat >$ED3 <<EOF3
161/^P_/{
162	s/^P_POUNDCif_/#if /
163	s/^P_POUNDCendif/#endif/
164	s/^P_//
165	b done
166}
167	s/		*/ /g
168	s/  */ /g
169	s/ ,/,/g
170	s/( /(/g
171	s/ )/)/g
172	s/ gen_/ /
173	s/^[ 	]*@[ 	]*@[ 	]*/	/
174:done
175EOF3
176
177if test "$USE" = generated ; then
178cat >$ED4 <<EOF
179	s/^\(.*\) \(.*\) (\(.*\))\$/NCURSES_EXPORT(\1) (\2) (\3)/
180EOF
181else
182cat >$ED4 <<EOF
183/^\(.*\) \(.*\) (\(.*\))\$/ {
184	h
185	s/^\(.*\) \(.*\) (\(.*\))\$/extern \1 call_\2 (\3);/
186	p
187	g
188	s/^\(.*\) \(.*\) (\(.*\))\$/\1 call_\2 (\3)/
189	}
190s/\([^_]\)NCURSES_SP_NAME___\([a-zA-Z][a-zA-Z_]*\)/\1NCURSES_SP_NAME(\2)/g
191EOF
192fi
193
194cat >$AW1 <<\EOF1
195BEGIN	{
196		skip=0;
197	}
198/^P_POUNDCif/ {
199		print "\n"
200		print $0
201		skip=0;
202}
203/^P_POUNDCendif/ {
204		print $0
205		skip=1;
206}
207$0 !~ /^P_/ {
208	if (skip)
209		print "\n"
210	skip=1;
211
212	first=$1
213	for (i = 1; i <= NF; i++) {
214		if ( $i != "NCURSES_CONST" ) {
215			first = i;
216			break;
217		}
218	}
219	second = first + 1;
220	returnCast = "";
221	if ( $first == "chtype" ) {
222		returnType = "Chtype";
223	} else if ( $first == "SCREEN" ) {
224		returnType = "SP";
225	} else if ( $first == "WINDOW" ) {
226		returnType = "Win";
227	} else if ( $first == "attr_t" || $second == "attrset" || $second == "standout" || $second == "standend" || $second == "wattrset" || $second == "wstandout" || $second == "wstandend" ) {
228		returnType = "IntAttr";
229		returnCast = "(attr_t)";
230	} else if ( $first == "bool" || $first == "NCURSES_BOOL" ) {
231		returnType = "Bool";
232	} else if ( $second == "*" ) {
233		returnType = "Ptr";
234	} else {
235		returnType = "Code";
236	}
237	myfunc = second;
238	for (i = second; i <= NF; i++) {
239		if ($i != "*") {
240			myfunc = i;
241			break;
242		}
243	}
244	print $0;
245	print "{";
246	argcount = 1;
247	check = NF - 1;
248	if ($check == "void")
249		argcount = 0;
250	if (argcount != 0) {
251		for (i = 1; i <= NF; i++)
252			if ($i == ",")
253				argcount++;
254	}
255
256	# suppress trace-code for functions that we cannot do properly here,
257	# since they return data.
258	dotrace = 1;
259	if ($myfunc ~ /innstr/)
260		dotrace = 0;
261	if ($myfunc ~ /innwstr/)
262		dotrace = 0;
263
264	# workaround functions that we do not parse properly
265	if ($myfunc ~ /ripoffline/) {
266		dotrace = 0;
267		argcount = 2;
268		if ($myfunc ~ /NCURSES_SP_NAME/) {
269			argcount = 3;
270		}
271	}
272	if ($myfunc ~ /wunctrl/) {
273		dotrace = 0;
274	}
275
276	call = "@@T((T_CALLED(\""
277	args = ""
278	comma = ""
279	num = 0;
280	pointer = 0;
281	va_list = 0;
282	varargs = 0;
283	argtype = ""
284	for (i = myfunc; i <= NF; i++) {
285		ch = $i;
286		if ( ch == "*" ) {
287			pointer = 1;
288		} else if ( ch == "va_list" ) {
289			va_list = 1;
290		} else if ( ch == "..." ) {
291			varargs = 1;
292		} else if ( ch == "char" ) {
293			argtype = "char";
294		} else if ( ch == "int" ) {
295			argtype = "int";
296		} else if ( ch == "short" ) {
297			argtype = "short";
298		} else if ( ch == "chtype" ) {
299			argtype = "chtype";
300		} else if ( ch == "attr_t" || ch == "NCURSES_ATTR_T" ) {
301			argtype = "attr";
302		}
303
304		if ( ch == "," || ch == ")" ) {
305			argcast = "";
306			if (va_list) {
307				call = call "%s"
308			} else if (varargs) {
309				call = call "%s"
310			} else if (pointer) {
311				if ( argtype == "char" ) {
312					call = call "%s"
313					comma = comma "_nc_visbuf2(" num ","
314					pointer = 0;
315				} else {
316					call = call "%p"
317					comma = comma "(const void *)"
318				}
319			} else if (argcount != 0) {
320				if ( argtype == "int" || argtype == "short" ) {
321					call = call "%d"
322					argtype = ""
323				} else if ( argtype != "" ) {
324					call = call "%s"
325					comma = comma "_trace" argtype "2(" num ","
326					if (argtype == "attr") {
327						argcast = "(chtype)";
328					}
329				} else {
330					call = call "%#lx"
331					comma = comma "(long)"
332				}
333			}
334			if (ch == ",") {
335				args = args comma "a" ++num;
336			} else if ( argcount != 0 ) {
337				if ( va_list ) {
338					args = args comma "\"va_list\""
339				} else if ( varargs ) {
340					args = args comma "\"...\""
341				} else {
342					args = args comma argcast "z"
343				}
344			}
345			call = call ch
346			if (pointer == 0 && argcount != 0 && argtype != "" )
347				args = args ")"
348			if (args != "")
349				comma = ", "
350			pointer = 0;
351			argtype = ""
352		}
353		if ( i == myfunc || ch == "(" )
354			call = call ch
355	}
356	call = call "\")"
357	if (args != "")
358		call = call ", " args
359	call = call ")); "
360
361	if (dotrace)
362		printf "%s", call
363
364	if (match($0, "^void")) {
365		call = ""
366	} else if (dotrace) {
367		call = sprintf("return%s( ", returnType);
368		if (returnCast != "") {
369			call = call returnCast;
370		}
371	} else {
372		call = "@@return ";
373	}
374
375	call = call $myfunc "(";
376	for (i = 1; i < argcount; i++) {
377		if (i != 1)
378			call = call ", ";
379		call = call "a" i;
380	}
381	if ( argcount != 0 && $check != "..." ) {
382		if (argcount != 1)
383			call = call ", ";
384		call = call "z";
385	}
386	if (!match($0, "^void"))
387		call = call ") ";
388	if (dotrace) {
389		call = call ")";
390	}
391	print call ";"
392
393	if (match($0, "^void"))
394		print "@@returnVoid;"
395	print "}";
396}
397EOF1
398
399cat >$AW2 <<EOF1
400BEGIN		{
401		print "/*"
402		print " * DO NOT EDIT THIS FILE BY HAND!"
403		printf " * It is generated by $0 %s.\n", "$USE"
404		if ( "$USE" == "generated" ) {
405			print " *"
406			print " * This is a file of trivial functions generated from macro"
407			print " * definitions in curses.h to satisfy the XSI Curses requirement"
408			print " * that every macro also exist as a callable function."
409			print " *"
410			print " * It will never be linked unless you call one of the entry"
411			print " * points with its normal macro definition disabled.  In that"
412			print " * case, if you have no shared libraries, it will indirectly"
413			print " * pull most of the rest of the library into your link image."
414		}
415		print " */"
416		print "#define NCURSES_ATTR_T int"
417		print "#include <ncurses_cfg.h>"
418		print ""
419		print "#undef NCURSES_NOMACROS	/* _this_ file uses macros */"
420		print ""
421		print "#include <curses.priv.h>"
422		print ""
423		print "#undef vw_scanw"
424		print "#undef vwscanw"
425		print ""
426		print "#undef vw_printw"
427		print "#undef vwprintw"
428		}
429/^DECLARATIONS/	{start = 1; next;}
430		{
431		if (start) {
432			if ( "$USE" == "generated" ) {
433				print \$0;
434			} else if ( \$0 ~ /^[{}]?\$/ ) {
435				print \$0;
436			} else if ( \$0 ~ /;/ ) {
437				print \$0;
438			} else {
439				calls[start] = \$0;
440				print \$0;
441				start++;
442			}
443		}
444		}
445END		{
446		if ( "$USE" != "generated" ) {
447			print "int main(void)"
448			print "{"
449			for (n = 1; n < start; ++n) {
450				value = calls[n];
451				if ( value !~ /P_POUNDC/ ) {
452					gsub(/[[:blank:]]+/," ",value);
453					sub(/^[[:alnum:]_]+ /,"",value);
454					sub(/^\* /,"",value);
455					gsub(/[[:alnum:]_]+ \* /,"",value);
456					gsub(/ (const) /," ",value);
457					gsub(/ (int|short|attr_t|chtype|wchar_t|NCURSES_BOOL|NCURSES_OUTC|NCURSES_OUTC_sp|va_list) /," ",value);
458					gsub(/ void /,"",value);
459					sub(/^/,"call_",value);
460					gsub(/ (a[[:digit:]]|z) /, " 0 ", value);
461					gsub(/ int[[:blank:]]*[(][^)]+[)][(][^)]+[)]/, "0", value);
462					printf "\t%s;\n", value;
463				} else {
464					print value;
465				}
466			}
467			print "	return 0;"
468			print "}"
469		}
470		}
471EOF1
472
473cat >$TMP <<EOF
474#include <ncurses_cfg.h>
475#undef NCURSES_NOMACROS
476#include <curses.h>
477#include <term.h>
478#include <unctrl.h>
479
480DECLARATIONS
481
482EOF
483
484sed -n -f $ED1 \
485| sed -e 's/NCURSES_EXPORT(\(.*\)) \(.*\) (\(.*\))/\1 \2(\3)/' \
486| sed -f $ED2 \
487| $AWK -f $AW1 using=$USE \
488| sed \
489	-e 's/ [ ]*$//g' \
490	-e 's/^\([a-zA-Z_][a-zA-Z_]*[ *]*\)/\1 gen_/' \
491	-e 's/gen_$//' \
492	-e 's/  / /g' >>$TMP
493
494$preprocessor $TMP 2>/dev/null \
495| sed \
496	-e 's/  / /g' \
497	-e 's/^ //' \
498	-e 's/_Bool/NCURSES_BOOL/g' \
499| $AWK -f $AW2 \
500| sed -f $ED3 \
501| sed \
502	-e 's/^.*T_CALLED.*returnCode( \([a-z].*) \));/	return \1;/' \
503	-e 's/^.*T_CALLED.*returnCode( \((wmove.*) \));/	return \1;/' \
504	-e 's/gen_//' \
505	-e 's/^[ 	]*#/#/' \
506	-e '/#ident/d' \
507	-e '/#line/d' \
508| sed -f $ED4
509