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