xref: /freebsd/contrib/ncurses/ncurses/base/MKlib_gen.sh (revision 21817992b3314c908ab50f0bb88d2ee750b9c4ac)
1#!/bin/sh
2#
3# MKlib_gen.sh -- generate sources from curses.h macro definitions
4#
5# ($Id: MKlib_gen.sh,v 1.73 2022/10/01 13:14:20 tom Exp $)
6#
7##############################################################################
8# Copyright 2018-2021,2022 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#
73# introduces spurious #line markers into the preprocessor output.  The result
74# appears in gcc 5.0 and (with modification) in 5.1, making it necessary to
75# determine if we are using gcc, and if so, what version because the proposed
76# solution uses a nonstandard option.
77#
78# As illustrated in
79#	https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60723
80#
81# gcc developers chose to ignore the problems with this, and summarized those
82# as "intriguing problems" in
83#	https://gcc.gnu.org/gcc-5/porting_to.html
84
85PRG=`echo "$1" | "$AWK" '{ sub(/^[ 	]*/,""); sub(/[ 	].*$/, ""); print; }' || exit 0`
86FSF=`("$PRG" --version 2>/dev/null || exit 0) | ${FGREP-grep -F} "Free Software Foundation" | head -n 1`
87ALL=`"$PRG" -dumpversion 2>/dev/null || exit 0`
88ONE=`echo "$ALL" | sed -e 's/[^0-9].*$//'`
89if test -n "$FSF" && test -n "$ALL" && test -n "$ONE" ; then
90	if test "$ONE" -ge 5 ; then
91		echo ".. adding -P option to work around $PRG $ALL" >&2
92		preprocessor="$preprocessor -P"
93	fi
94fi
95
96PID=$$
97ED1=sed1_${PID}.sed
98ED2=sed2_${PID}.sed
99ED3=sed3_${PID}.sed
100ED4=sed4_${PID}.sed
101AW1=awk1_${PID}.awk
102AW2=awk2_${PID}.awk
103TMP=gen__${PID}.c
104trap "rm -f $ED1 $ED2 $ED3 $ED4 $AW1 $AW2 $TMP; exit 1" 1 2 3 15
105trap "rm -f $ED1 $ED2 $ED3 $ED4 $AW1 $AW2 $TMP" 0
106
107ALL=$USE
108if test "$USE" = implemented ; then
109	cat >$ED1 <<EOF1
110/^extern.*implemented/{
111	h
112	s/GCC_DEPRECATED([^)]*)//
113	s/NCURSES_SP_NAME(\([^)]*\))/NCURSES_SP_NAME___\1/
114	h
115	s/^.*implemented:\([^ 	*]*\).*/P_POUNDCif_USE_\1_SUPPORT/p
116	g
117	s/^extern \([^;]*\);.*/\1/p
118	g
119	s/^.*implemented:\([^ 	*]*\).*/P_POUNDCendif/p
120}
121/^extern.*generated/{
122	h
123	s/^.*generated:\([^ 	*]*\).*/P_POUNDCif_USE_\1_SUPPORT/p
124	g
125	s/^extern \([^;]*\);.*/\1/p
126	g
127	s/^.*generated:\([^ 	*]*\).*/P_POUNDCendif/p
128}
129EOF1
130else
131	cat >$ED1 <<EOF1
132/^extern.*${ALL}/{
133	h
134	s/^.*${ALL}:\([^ 	*]*\).*/P_POUNDCif_USE_\1_SUPPORT/p
135	g
136	s/^extern \([^;]*\);.*/\1/p
137	g
138	s/^.*${ALL}:\([^ 	*]*\).*/P_POUNDCendif/p
139}
140EOF1
141fi
142
143cat >$ED2 <<EOF2
144/^P_/b nc
145/(void)/b nc
146	s/,/ a1% /
147	s/,/ a2% /
148	s/,/ a3% /
149	s/,/ a4% /
150	s/,/ a5% /
151	s/,/ a6% /
152	s/,/ a7% /
153	s/,/ a8% /
154	s/,/ a9% /
155	s/,/ a10% /
156	s/,/ a11% /
157	s/,/ a12% /
158	s/,/ a13% /
159	s/,/ a14% /
160	s/,/ a15% /
161	s/*/ * /g
162	s/%/ , /g
163	s/)/ z)/
164	s/\.\.\. z)/...)/
165:nc
166	s/(/ ( /
167	s/)/ )/
168EOF2
169
170cat >$ED3 <<EOF3
171/^P_/{
172	s/^P_POUNDCif_/#if /
173	s/^P_POUNDCendif/#endif/
174	s/^P_//
175	b done
176}
177	s/		*/ /g
178	s/  */ /g
179	s/ ,/,/g
180	s/( /(/g
181	s/ )/)/g
182	s/ gen_/ /
183	s/^[ 	]*@[ 	]*@[ 	]*/	/
184:done
185EOF3
186
187if test "$USE" = generated ; then
188cat >$ED4 <<EOF
189	s/^\(.*\) \(.*\) (\(.*\))\$/NCURSES_EXPORT(\1) \2 (\3)/
190	/attr_[sg]et.* z)/s,z),z GCC_UNUSED),
191EOF
192else
193cat >$ED4 <<EOF
194/^\(.*\) \(.*\) (\(.*\))\$/ {
195	h
196	s/^\(.*\) \(.*\) (\(.*\))\$/extern \1 call_\2 (\3);/
197	p
198	g
199	s/^\(.*\) \(.*\) (\(.*\))\$/\1 call_\2 (\3)/
200	}
201s/\([^_]\)NCURSES_SP_NAME___\([a-zA-Z][a-zA-Z_]*\)/\1NCURSES_SP_NAME(\2)/g
202EOF
203fi
204
205cat >$AW1 <<\EOF1
206BEGIN	{
207		skip=0;
208	}
209/^P_POUNDCif/ {
210		print "\n"
211		print $0
212		skip=0;
213}
214/^P_POUNDCendif/ {
215		print $0
216		skip=1;
217}
218$0 !~ /^P_/ {
219	if (skip)
220		print "\n"
221	skip=1;
222
223	first=$1
224	for (i = 1; i <= NF; i++) {
225		if ( $i != "NCURSES_CONST" ) {
226			first = i;
227			break;
228		}
229	}
230	second = first + 1;
231	returnCast = "";
232	if ( $first == "chtype" ) {
233		returnType = "Chtype";
234	} else if ( $first == "SCREEN" ) {
235		returnType = "SP";
236	} else if ( $first == "WINDOW" ) {
237		returnType = "Win";
238	} else if ( $first == "attr_t" || $second == "attrset" || $second == "standout" || $second == "standend" || $second == "wattrset" || $second == "wstandout" || $second == "wstandend" ) {
239		returnType = "IntAttr";
240		returnCast = "(attr_t)";
241	} else if ( $first == "bool" || $first == "NCURSES_BOOL" ) {
242		returnType = "Bool";
243	} else if ( $second == "*" ) {
244		returnType = ($1 == "NCURSES_CONST") ? "CPtr" : "Ptr";
245	} else {
246		returnType = "Code";
247	}
248	myfunc = second;
249	for (i = second; i <= NF; i++) {
250		if ($i != "*") {
251			myfunc = i;
252			break;
253		}
254	}
255	if (using == "implemented") {
256		printf "#undef %s\n", $myfunc;
257	}
258	print $0;
259	print "{";
260	argcount = 1;
261	check = NF - 1;
262	if ($check == "void")
263		argcount = 0;
264	if (argcount != 0) {
265		for (i = 1; i <= NF; i++)
266			if ($i == ",")
267				argcount++;
268	}
269
270	# suppress trace-code for functions that we cannot do properly here,
271	# since they return data.
272	dotrace = 1;
273	if ($myfunc ~ /innstr/)
274		dotrace = 0;
275	if ($myfunc ~ /innwstr/)
276		dotrace = 0;
277
278	# workaround functions that we do not parse properly
279	if ($myfunc ~ /ripoffline/) {
280		dotrace = 0;
281		argcount = 2;
282		if ($myfunc ~ /NCURSES_SP_NAME/) {
283			argcount = 3;
284		}
285	}
286	if ($myfunc ~ /wunctrl/) {
287		dotrace = 0;
288	}
289
290	do_getstr = 0;
291	if ($myfunc ~ /get[n]?str/) {
292		do_getstr = 1;
293	}
294
295	call = "@@T((T_CALLED(\""
296	args = ""
297	comma = ""
298	num = 0;
299	pointer = 0;
300	va_list = 0;
301	varargs = 0;
302	argtype = ""
303	for (i = myfunc; i <= NF; i++) {
304		ch = $i;
305		if ( ch == "*" ) {
306			pointer = 1;
307		} else if ( ch == "va_list" ) {
308			va_list = 1;
309		} else if ( ch == "..." ) {
310			varargs = 1;
311		} else if ( ch == "char" ) {
312			argtype = "char";
313		} else if ( ch == "int" ) {
314			argtype = "int";
315		} else if ( ch == "short" ) {
316			argtype = "short";
317		} else if ( ch == "chtype" ) {
318			argtype = "chtype";
319		} else if ( ch == "attr_t" || ch == "NCURSES_ATTR_T" ) {
320			argtype = "attr";
321		}
322
323		if ( ch == "," || ch == ")" ) {
324			argcast = "";
325			if (va_list) {
326				call = call "%s"
327			} else if (varargs) {
328				call = call "%s"
329			} else if (pointer) {
330				if ( argtype == "char" ) {
331					if (do_getstr) {
332						call = call "%p"
333					} else {
334						call = call "%s"
335					}
336					comma = comma "_nc_visbuf2(" num ","
337					pointer = 0;
338				} else {
339					call = call "%p"
340					comma = comma "(const void *)"
341				}
342			} else if (argcount != 0) {
343				if ( argtype == "int" || argtype == "short" ) {
344					call = call "%d"
345					argtype = ""
346				} else if ( argtype != "" ) {
347					call = call "%s"
348					comma = comma "_trace" argtype "2(" num ","
349					if (argtype == "attr") {
350						argcast = "(chtype)";
351					}
352				} else {
353					call = call "%#lx"
354					comma = comma "(long)"
355				}
356			}
357			if (ch == ",") {
358				args = args comma "a" ++num;
359			} else if ( argcount != 0 ) {
360				if ( va_list ) {
361					args = args comma "\"va_list\""
362				} else if ( varargs ) {
363					args = args comma "\"...\""
364				} else {
365					args = args comma argcast "z"
366				}
367			}
368			call = call ch
369			if (pointer == 0 && argcount != 0 && argtype != "" )
370				args = args ")"
371			if (args != "")
372				comma = ", "
373			pointer = 0;
374			argtype = ""
375		}
376		if ( i == myfunc || ch == "(" )
377			call = call ch
378	}
379	call = call "\")"
380	if (args != "")
381		call = call ", " args
382	call = call ")); "
383
384	if (dotrace)
385		printf "%s\n\t@@", call
386
387	if (match($0, "^void")) {
388		call = ""
389	} else if (dotrace) {
390		call = sprintf("return%s( ", returnType);
391		if (returnCast != "") {
392			call = call returnCast;
393		}
394	} else {
395		call = "@@return ";
396	}
397
398	call = call $myfunc "(";
399	for (i = 1; i < argcount; i++) {
400		if (i != 1)
401			call = call ", ";
402		call = call "a" i;
403	}
404	if ( argcount != 0 && $check != "..." ) {
405		if (argcount != 1)
406			call = call ", ";
407		call = call "z";
408	}
409	if (!match($0, "^void"))
410		call = call ") ";
411	if (dotrace) {
412		call = call ")";
413	}
414	print call ";"
415
416	if (match($0, "^void"))
417		print "@@returnVoid;"
418	print "}";
419}
420EOF1
421
422cat >$AW2 <<EOF1
423BEGIN		{
424		printf "/* This file was generated by $0 $USE */\n"
425		print ""
426		print "/*"
427		print " * DO NOT EDIT THIS FILE BY HAND!"
428		if ( "$USE" == "generated" ) {
429			print " *"
430			print " * This is a file of trivial functions generated from macro"
431			print " * definitions in curses.h to satisfy the XSI Curses requirement"
432			print " * that every macro also exist as a callable function."
433			print " *"
434			print " * It will never be linked unless you call one of the entry"
435			print " * points with its normal macro definition disabled.  In that"
436			print " * case, if you have no shared libraries, it will indirectly"
437			print " * pull most of the rest of the library into your link image."
438		}
439		print " */"
440		print "#define NCURSES_ATTR_T int"
441		print "#include <ncurses_cfg.h>"
442		print ""
443		print "#undef NCURSES_NOMACROS	/* _this_ file uses macros */"
444		print "#define NCURSES_NOMACROS 1"
445		print ""
446		print "#include <curses.priv.h>"
447		print ""
448		}
449/^DECLARATIONS/	{start = 1; next;}
450		{
451		if (start) {
452			if ( "$USE" == "generated" ) {
453				print \$0;
454			} else if ( \$0 ~ /^[{}]?\$/ ) {
455				print \$0;
456			} else if ( \$0 ~ /;/ ) {
457				print \$0;
458			} else {
459				calls[start] = \$0;
460				print \$0;
461				start++;
462			}
463		}
464		}
465END		{
466		if ( "$USE" != "generated" ) {
467			print "int main(void)"
468			print "{"
469			for (n = 1; n < start; ++n) {
470				value = calls[n];
471				if ( value !~ /P_POUNDC/ ) {
472					gsub(/[ \t]+/," ",value);
473					sub(/^[0-9a-zA-Z_]+ /,"",value);
474					sub(/^[*][ \t]*/,"",value);
475					gsub("struct[ \t]*[0-9a-zA-Z_]+[ \t]*[*]","",value);
476					gsub(/[0-9a-zA-Z_]+[ \t]*[*][ \t]*/,"",value);
477					gsub(/ (const) /," ",value);
478					gsub(/ (int|short|attr_t|chtype|wchar_t|NCURSES_BOOL|NCURSES_OUTC|NCURSES_OUTC_sp|va_list) /," ",value);
479					gsub(/ void /,"",value);
480					sub(/^/,"call_",value);
481					gsub(/ (a[0-9]|z) /, " 0 ", value);
482					gsub(/ int[ \t]*[(][^)]+[)][(][^)]+[)]/, "0", value);
483					printf "\t%s;\n", value;
484				} else {
485					print value;
486				}
487			}
488			print "	return 0;"
489			print "}"
490		}
491		}
492EOF1
493
494cat >$TMP <<EOF
495#include <ncurses_cfg.h>
496#undef NCURSES_NOMACROS
497#include <curses.h>
498#include <term.h>
499#include <unctrl.h>
500
501DECLARATIONS
502
503EOF
504
505sed -n -f $ED1 \
506| sed -e 's/NCURSES_EXPORT(\(.*\)) \(.*\) (\(.*\))/\1 \2(\3)/' \
507| sed -f $ED2 \
508| "$AWK" -f $AW1 using="$USE" \
509| sed \
510	-e 's/ [ ]*$//g' \
511	-e 's/^\([a-zA-Z_][a-zA-Z_]*[ *]*\)/\1 gen_/' \
512	-e 's/gen_$//' \
513	-e 's/  / /g' >>$TMP
514
515$preprocessor $TMP 2>/dev/null \
516| sed \
517	-e 's/  / /g' \
518	-e 's/^ //' \
519	-e 's/_Bool/NCURSES_BOOL/g' \
520| "$AWK" -f $AW2 \
521| sed -f $ED3 \
522| sed \
523	-e 's/^.*T_CALLED.*returnCode( \([a-z].*) \));/	return \1;/' \
524	-e 's/^.*T_CALLED.*returnCode( \((wmove.*) \));/	return \1;/' \
525	-e 's/gen_//' \
526	-e 's/^[ 	]*#/#/' \
527	-e '/#ident/d' \
528	-e '/#line/d' \
529| sed -f $ED4
530