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