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