xref: /src/contrib/ncurses/ncurses/base/MKlib_gen.sh (revision 68ad2b0d7af2a3571c4abac9afa712f9b09b721c)
1#!/bin/sh
2#
3# MKlib_gen.sh -- generate sources from curses.h macro definitions
4#
5# ($Id: MKlib_gen.sh,v 1.79 2025/02/23 01:55:06 tom Exp $)
6#
7##############################################################################
8# Copyright 2018-2024,2025 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),
191	s/\(((\)0[L]*\([ ]*!=[ ]*(const void\)/\1NULL\2/g
192	/returnCode(wborder_set/s,0[L]*,NULL,g
193	/returnWin/s,0[L]*,NULL,
194	/_parent/s,0[L]*,NULL,
195EOF
196else
197cat >$ED4 <<EOF
198/^\(.*\) \(.*\) (\(.*\))\$/ {
199	h
200	s/^\(.*\) \(.*\) (\(.*\))\$/extern \1 call_\2 (\3);/
201	p
202	g
203	s/^\(.*\) \(.*\) (\(.*\))\$/\1 call_\2 (\3)/
204	}
205s/\([^_]\)NCURSES_SP_NAME___\([a-zA-Z][a-zA-Z_]*\)/\1NCURSES_SP_NAME(\2)/g
206/call_NCURSES_SP_NAME/s,(0,(NULL,
207/call\(_NCURSES_SP_NAME\)\?_*\(delscreen\|ripoffline\|set_term\|vidputs\|vid_puts\)/s,0),NULL),
208EOF
209fi
210
211cat >$AW1 <<\EOF1
212BEGIN	{
213		skip=0;
214	}
215/^P_POUNDCif/ {
216		print "\n"
217		print $0
218		skip=0;
219}
220/^P_POUNDCendif/ {
221		print $0
222		skip=1;
223}
224$0 !~ /^P_/ {
225	if (skip)
226		print "\n"
227	skip=1;
228
229	first=$1
230	for (i = 1; i <= NF; i++) {
231		if ( $i != "NCURSES_CONST" ) {
232			first = i;
233			break;
234		}
235	}
236	second = first + 1;
237	returnCast = "";
238	if ( $first == "chtype" ) {
239		returnType = "Chtype";
240	} else if ( $first == "SCREEN" ) {
241		returnType = "SP";
242	} else if ( $first == "WINDOW" ) {
243		returnType = "Win";
244	} else if ( $first == "attr_t" || $second == "attrset" || $second == "standout" || $second == "standend" || $second == "wattrset" || $second == "wstandout" || $second == "wstandend" ) {
245		returnType = "IntAttr";
246		returnCast = "(attr_t)";
247	} else if ( $first == "bool" || $first == "NCURSES_BOOL" ) {
248		returnType = "Bool";
249	} else if ( $second == "*" ) {
250		returnType = ($1 == "NCURSES_CONST") ? "CPtr" : "Ptr";
251	} else {
252		returnType = "Code";
253	}
254	myfunc = second;
255	for (i = second; i <= NF; i++) {
256		if ($i != "*") {
257			myfunc = i;
258			break;
259		}
260	}
261	if (using == "implemented") {
262		printf "#undef %s\n", $myfunc;
263	}
264	print $0;
265	print "{";
266	argcount = 1;
267	check = NF - 1;
268	if ($check == "void")
269		argcount = 0;
270	if (argcount != 0) {
271		for (i = 1; i <= NF; i++)
272			if ($i == ",")
273				argcount++;
274	}
275
276	# suppress trace-code for functions that we cannot do properly here,
277	# since they return data.
278	dotrace = 1;
279	if ($myfunc ~ /innstr/)
280		dotrace = 0;
281	if ($myfunc ~ /innwstr/)
282		dotrace = 0;
283
284	# workaround functions that we do not parse properly
285	if ($myfunc ~ /ripoffline/) {
286		dotrace = 0;
287		argcount = 2;
288		if ($myfunc ~ /NCURSES_SP_NAME/) {
289			argcount = 3;
290		}
291	}
292	if ($myfunc ~ /wunctrl/) {
293		dotrace = 0;
294	}
295
296	do_getstr = 0;
297	if ($myfunc ~ /get[n]?str/) {
298		do_getstr = 1;
299	}
300
301	call = "@@T((T_CALLED(\""
302	args = ""
303	comma = ""
304	num = 0;
305	pointer = 0;
306	va_list = 0;
307	varargs = 0;
308	argtype = ""
309	for (i = myfunc; i <= NF; i++) {
310		ch = $i;
311		if ( ch == "*" ) {
312			pointer = 1;
313		} else if ( ch == "va_list" ) {
314			va_list = 1;
315		} else if ( ch == "..." ) {
316			varargs = 1;
317		} else if ( ch == "char" ) {
318			argtype = "char";
319		} else if ( ch == "int" ) {
320			argtype = "int";
321		} else if ( ch == "short" ) {
322			argtype = "short";
323		} else if ( ch == "chtype" ) {
324			argtype = "chtype";
325		} else if ( ch == "attr_t" || ch == "NCURSES_ATTR_T" ) {
326			argtype = "attr";
327		}
328
329		if ( ch == "," || ch == ")" ) {
330			argcast = "";
331			if (va_list) {
332				call = call "%s"
333			} else if (varargs) {
334				call = call "%s"
335			} else if (pointer) {
336				if ( argtype == "char" ) {
337					if (do_getstr) {
338						call = call "%p"
339					} else {
340						call = call "%s"
341					}
342					comma = comma "_nc_visbuf2(" num ","
343					pointer = 0;
344				} else {
345					call = call "%p"
346					comma = comma "(const void *)"
347				}
348			} else if (argcount != 0) {
349				if ( argtype == "int" || argtype == "short" ) {
350					call = call "%d"
351					argtype = ""
352				} else if ( argtype != "" ) {
353					call = call "%s"
354					comma = comma "_trace" argtype "2(" num ","
355					if (argtype == "attr") {
356						argcast = "(chtype)";
357					}
358				} else {
359					call = call "%#lx"
360					comma = comma "(long)"
361				}
362			}
363			if (ch == ",") {
364				args = args comma "a" ++num;
365			} else if ( argcount != 0 ) {
366				if ( va_list ) {
367					args = args comma "\"va_list\""
368				} else if ( varargs ) {
369					args = args comma "\"...\""
370				} else {
371					args = args comma argcast "z"
372				}
373			}
374			call = call ch
375			if (pointer == 0 && argcount != 0 && argtype != "" )
376				args = args ")"
377			if (args != "")
378				comma = ", "
379			pointer = 0;
380			argtype = ""
381		}
382		if ( i == myfunc || ch == "(" )
383			call = call ch
384	}
385	call = call "\")"
386	if (args != "")
387		call = call ", " args
388	call = call ")); "
389
390	if (dotrace)
391		printf "%s\n\t@@", call
392
393	if (match($0, "^void")) {
394		call = ""
395	} else if (dotrace) {
396		call = sprintf("return%s( ", returnType);
397		if (returnCast != "") {
398			call = call returnCast;
399		}
400	} else {
401		call = "@@return ";
402	}
403
404	call = call $myfunc "(";
405	for (i = 1; i < argcount; i++) {
406		if (i != 1)
407			call = call ", ";
408		call = call "a" i;
409	}
410	if ( argcount != 0 && $check != "..." ) {
411		if (argcount != 1)
412			call = call ", ";
413		call = call "z";
414	}
415	if (!match($0, "^void"))
416		call = call ") ";
417	if (dotrace) {
418		call = call ")";
419	}
420	print call ";"
421
422	if (match($0, "^void"))
423		print "@@returnVoid;"
424	print "}";
425}
426EOF1
427
428cat >$AW2 <<EOF1
429BEGIN		{
430		printf "/* This file was generated by $0 $USE */\n"
431		print ""
432		print "/*"
433		print " * DO NOT EDIT THIS FILE BY HAND!"
434		if ( "$USE" == "generated" ) {
435			print " *"
436			print " * This is a file of trivial functions generated from macro"
437			print " * definitions in curses.h to satisfy the XSI Curses requirement"
438			print " * that every macro also exist as a callable function."
439			print " *"
440			print " * It will never be linked unless you call one of the entry"
441			print " * points with its normal macro definition disabled.  In that"
442			print " * case, if you have no shared libraries, it will indirectly"
443			print " * pull most of the rest of the library into your link image."
444		}
445		print " */"
446		print "#define NCURSES_ATTR_T int"
447		print "#include <ncurses_cfg.h>"
448		print ""
449		print "#if USE_STDBOOL_H"
450		print "#include <stdbool.h>"
451		print "#endif"
452		print ""
453		print "#undef NCURSES_NOMACROS	/* _this_ file uses macros */"
454		print "#define NCURSES_NOMACROS 1"
455		print ""
456		print "#include <curses.priv.h>"
457		print ""
458		}
459/^DECLARATIONS/	{start = 1; next;}
460		{
461		if (start) {
462			if ( "$USE" == "generated" ) {
463				print \$0;
464			} else if ( \$0 ~ /^[{}]?\$/ ) {
465				print \$0;
466			} else if ( \$0 ~ /;/ ) {
467				print \$0;
468			} else {
469				calls[start] = \$0;
470				print \$0;
471				start++;
472			}
473		}
474		}
475END		{
476		if ( "$USE" != "generated" ) {
477			print  "static int link_test(int code)"
478			print  "{"
479			print  "  switch(code)"
480			print  "  {"
481			casenum = 1;
482			for (n = 1; n < start; ++n) {
483				value = calls[n];
484				if ( value !~ /P_POUNDC/ ) {
485					gsub(/[ \t]+/," ",value);
486					sub(/^[0-9a-zA-Z_]+ /,"",value);
487					sub(/^[*][ \t]*/,"",value);
488					gsub("struct[ \t]*[0-9a-zA-Z_]+[ \t]*[*]","",value);
489					arg_l = index(value, "(");
490					arg_r = index(value, ")");
491					if ( arg_l > 0 && arg_r > arg_l + 1 ) {
492						args = substr(value, arg_l + 1, arg_r - arg_l - 1);
493						gsub(/[0-9a-zA-Z_]+[ \t]*[*][ \t]*[0-9a-zA-Z_]+/,"NULL",args);
494						gsub(/ (bool|int|short|attr_t|chtype|wchar_t|NCURSES_BOOL|NCURSES_OUTC|NCURSES_OUTC_sp|va_list) /," ",args);
495						value = substr(value,0,arg_l) args substr(value,arg_r);
496					}
497					gsub(/[0-9a-zA-Z_]+[ \t]*[*][ \t]*/,"",value);
498					gsub(/ (const) /," ",value);
499					gsub(/ (bool|int|short|attr_t|chtype|wchar_t|NCURSES_BOOL|NCURSES_OUTC|NCURSES_OUTC_sp|va_list) /," ",value);
500					gsub(/ void /,"",value);
501					sub(/^/,"call_",value);
502					gsub(/ (a[0-9]|z) /, " 0 ", value);
503					gsub(/ int[ \t]*[(][^)]+[)][(][^)]+[)]/, "0", value);
504					if ( index(value, "call_NCURSES_SP_NAME") > 0 ) {
505						sub("0","NULL", value);
506					}
507					printf "    case %d: %s; break;\n", casenum++, value;
508				} else {
509					if ( index(value, "call_NCURSES_SP_NAME") > 0 ) {
510						printf "/* FIXME %s */\n", value;
511						sub("0","NULL", value);
512					}
513					print value;
514				}
515			}
516			print  "  default: return 0; /* case did not exist */"
517			print  "  }"
518			print  "  return 1; /* case exists */"
519			print  "}"
520		}
521		}
522EOF1
523
524cat >$TMP <<EOF
525#include <ncurses_cfg.h>
526#undef NCURSES_NOMACROS
527#include <curses.h>
528#include <term.h>
529#include <unctrl.h>
530
531DECLARATIONS
532
533EOF
534
535sed -n -f $ED1 \
536| sed -e 's/NCURSES_EXPORT(\(.*\)) \(.*\) (\(.*\))/\1 \2(\3)/' \
537| sed -f $ED2 \
538| "$AWK" -f $AW1 using="$USE" \
539| sed \
540	-e 's/ [ ]*$//g' \
541	-e 's/^\([a-zA-Z_][a-zA-Z_]*[ *]*\)/\1 gen_/' \
542	-e 's/gen_$//' \
543	-e 's/  / /g' >>$TMP
544
545$preprocessor $TMP 2>/dev/null \
546| sed \
547	-e 's/  / /g' \
548	-e 's/^ //' \
549	-e 's/_Bool/NCURSES_BOOL/g' \
550| "$AWK" -f $AW2 \
551| sed -f $ED3 \
552| sed \
553	-e 's/^.*T_CALLED.*returnCode( \([a-z].*) \));/	return \1;/' \
554	-e 's/^.*T_CALLED.*returnCode( \((wmove.*) \));/	return \1;/' \
555	-e 's/gen_//' \
556	-e 's/^[ 	]*#/#/' \
557	-e '/#ident/d' \
558	-e '/#line/d' \
559| sed -f $ED4
560
561# a simple test-driver checks one or all of the linkages
562if test "$USE" = "implemented"
563then
564cat <<"EOF"
565int main(int argc, char *argv[])
566{
567	int n;
568	int rc;
569	if (argc > 1)
570	{
571		rc = !link_test(atoi(argv[1]));
572	}
573	else
574	{
575		rc = 0;
576		for (n = 1; ; ++n)
577		{
578			printf("TEST %d\n", n);
579			fflush(stdout);
580			if (!link_test(n))
581			{
582				rc = 1;
583				break;
584			}
585		}
586	}
587	return rc;
588}
589EOF
590fi
591