1 /* $OpenBSD: comp_parse.c,v 1.1 1999/01/18 19:10:14 millert Exp $ */ 2 3 /**************************************************************************** 4 * Copyright (c) 1998 Free Software Foundation, Inc. * 5 * * 6 * Permission is hereby granted, free of charge, to any person obtaining a * 7 * copy of this software and associated documentation files (the * 8 * "Software"), to deal in the Software without restriction, including * 9 * without limitation the rights to use, copy, modify, merge, publish, * 10 * distribute, distribute with modifications, sublicense, and/or sell * 11 * copies of the Software, and to permit persons to whom the Software is * 12 * furnished to do so, subject to the following conditions: * 13 * * 14 * The above copyright notice and this permission notice shall be included * 15 * in all copies or substantial portions of the Software. * 16 * * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 20 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 21 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 22 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 23 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 24 * * 25 * Except as contained in this notice, the name(s) of the above copyright * 26 * holders shall not be used in advertising or otherwise to promote the * 27 * sale, use or other dealings in this Software without prior written * 28 * authorization. * 29 ****************************************************************************/ 30 31 /**************************************************************************** 32 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 33 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 34 ****************************************************************************/ 35 36 37 38 /* 39 * comp_parse.c -- parser driver loop and use handling. 40 * 41 * _nc_read_entry_source(FILE *, literal, bool, bool (*hook)()) 42 * _nc_resolve_uses(void) 43 * _nc_free_entries(void) 44 * 45 * Use this code by calling _nc_read_entry_source() on as many source 46 * files as you like (either terminfo or termcap syntax). If you 47 * want use-resolution, call _nc_resolve_uses(). To free the list 48 * storage, do _nc_free_entries(). 49 * 50 */ 51 52 #include <curses.priv.h> 53 54 #include <ctype.h> 55 56 #include <tic.h> 57 #include <term.h> 58 #include <term_entry.h> 59 60 MODULE_ID("$From: comp_parse.c,v 1.23 1998/05/30 23:38:15 Todd.Miller Exp $") 61 62 static void sanity_check(TERMTYPE *); 63 64 /**************************************************************************** 65 * 66 * Entry queue handling 67 * 68 ****************************************************************************/ 69 /* 70 * The entry list is a doubly linked list with NULLs terminating the lists: 71 * 72 * --------- --------- --------- 73 * | | | | | | offset 74 * |-------| |-------| |-------| 75 * | ----+-->| ----+-->| NULL | next 76 * |-------| |-------| |-------| 77 * | NULL |<--+---- |<--+---- | last 78 * --------- --------- --------- 79 * ^ ^ 80 * | | 81 * | | 82 * _nc_head _nc_tail 83 */ 84 85 ENTRY *_nc_head, *_nc_tail; 86 87 static void enqueue(ENTRY *ep) 88 /* add an entry to the in-core list */ 89 { 90 ENTRY *newp = (ENTRY *)malloc(sizeof(ENTRY)); 91 92 if (newp == NULL) 93 _nc_err_abort("Out of memory"); 94 95 (void) memcpy(newp, ep, sizeof(ENTRY)); 96 97 newp->last = _nc_tail; 98 _nc_tail = newp; 99 100 newp->next = (ENTRY *)NULL; 101 if (newp->last) 102 newp->last->next = newp; 103 } 104 105 void _nc_free_entries(ENTRY *head) 106 /* free the allocated storage consumed by list entries */ 107 { 108 ENTRY *ep, *next; 109 110 for (ep = head; ep; ep = next) 111 { 112 /* 113 * This conditional lets us disconnect storage from the list. 114 * To do this, copy an entry out of the list, then null out 115 * the string-table member in the original and any use entries 116 * it references. 117 */ 118 FreeIfNeeded(ep->tterm.str_table); 119 120 next = ep->next; 121 122 free(ep); 123 if (ep == _nc_head) _nc_head = 0; 124 if (ep == _nc_tail) _nc_tail = 0; 125 } 126 } 127 128 bool _nc_entry_match(char *n1, char *n2) 129 /* do any of the aliases in a pair of terminal names match? */ 130 { 131 char *pstart, *qstart, *pend, *qend; 132 char nc1[MAX_NAME_SIZE+1], nc2[MAX_NAME_SIZE+1]; 133 size_t n; 134 135 if (strchr(n1, '|') == NULL) 136 { 137 if ((n = strlcpy(nc1, n1, sizeof(nc1))) > sizeof(nc1) - 2) 138 n = sizeof(nc1) - 2; 139 nc1[n++] = '|'; 140 nc1[n] = '\0'; 141 n1 = nc1; 142 } 143 144 if (strchr(n2, '|') == NULL) 145 { 146 if ((n = strlcpy(nc2, n2, sizeof(nc2))) > sizeof(nc2) - 2) 147 n = sizeof(nc2) - 2; 148 nc2[n++] = '|'; 149 nc2[n] = '\0'; 150 n2 = nc2; 151 } 152 153 for (pstart = n1; (pend = strchr(pstart, '|')); pstart = pend + 1) 154 for (qstart = n2; (qend = strchr(qstart, '|')); qstart = qend + 1) 155 if ((pend-pstart == qend-qstart) 156 && memcmp(pstart, qstart, (size_t)(pend-pstart)) == 0) 157 return(TRUE); 158 159 return(FALSE); 160 } 161 162 /**************************************************************************** 163 * 164 * Entry compiler and resolution logic 165 * 166 ****************************************************************************/ 167 168 void _nc_read_entry_source(FILE *fp, char *buf, 169 int literal, bool silent, 170 bool (*hook)(ENTRY *)) 171 /* slurp all entries in the given file into core */ 172 { 173 ENTRY thisentry; 174 bool oldsuppress = _nc_suppress_warnings; 175 int immediate = 0; 176 177 if (silent) 178 _nc_suppress_warnings = TRUE; /* shut the lexer up, too */ 179 180 for (_nc_reset_input(fp, buf); _nc_parse_entry(&thisentry, literal, silent) != ERR; ) 181 { 182 if (!isalnum(thisentry.tterm.term_names[0])) 183 _nc_err_abort("terminal names must start with letter or digit"); 184 185 /* 186 * This can be used for immediate compilation of entries with no 187 * use references to disk, so as to avoid chewing up a lot of 188 * core when the resolution code could fetch entries off disk. 189 */ 190 if (hook != NULLHOOK && (*hook)(&thisentry)) 191 immediate++; 192 else 193 enqueue(&thisentry); 194 } 195 196 if (_nc_tail) 197 { 198 /* set up the head pointer */ 199 for (_nc_head = _nc_tail; _nc_head->last; _nc_head = _nc_head->last) 200 continue; 201 202 DEBUG(1, ("head = %s", _nc_head->tterm.term_names)); 203 DEBUG(1, ("tail = %s", _nc_tail->tterm.term_names)); 204 } 205 #ifdef TRACE 206 else if (!immediate) 207 DEBUG(1, ("no entries parsed")); 208 #endif 209 210 _nc_suppress_warnings = oldsuppress; 211 } 212 213 int _nc_resolve_uses(void) 214 /* try to resolve all use capabilities */ 215 { 216 ENTRY *qp, *rp, *lastread = NULL; 217 bool keepgoing; 218 int i, j, unresolved, total_unresolved, multiples; 219 220 DEBUG(2, ("RESOLUTION BEGINNING")); 221 222 /* 223 * Check for multiple occurrences of the same name. 224 */ 225 multiples = 0; 226 for_entry_list(qp) 227 { 228 int matchcount = 0; 229 230 for_entry_list(rp) 231 if (qp > rp 232 && _nc_entry_match(qp->tterm.term_names, rp->tterm.term_names)) 233 { 234 matchcount++; 235 if (matchcount == 1) 236 { 237 (void) fprintf(stderr, "Name collision between %s", 238 _nc_first_name(qp->tterm.term_names)); 239 multiples++; 240 } 241 if (matchcount >= 1) 242 (void) fprintf(stderr, " %s", _nc_first_name(rp->tterm.term_names)); 243 } 244 if (matchcount >= 1) 245 (void) putc('\n', stderr); 246 } 247 if (multiples > 0) 248 return(FALSE); 249 250 DEBUG(2, ("NO MULTIPLE NAME OCCURRENCES")); 251 252 /* 253 * First resolution stage: replace names in use arrays with entry 254 * pointers. By doing this, we avoid having to do the same name 255 * match once for each time a use entry is itself unresolved. 256 */ 257 total_unresolved = 0; 258 _nc_curr_col = -1; 259 for_entry_list(qp) 260 { 261 unresolved = 0; 262 for (i = 0; i < qp->nuses; i++) 263 { 264 bool foundit; 265 char *child = _nc_first_name(qp->tterm.term_names); 266 char *lookfor = (char *)(qp->uses[i].parent); 267 long lookline = qp->uses[i].line; 268 269 foundit = FALSE; 270 271 _nc_set_type(child); 272 273 /* first, try to resolve from in-core records */ 274 for_entry_list(rp) 275 if (rp != qp 276 && _nc_name_match(rp->tterm.term_names, lookfor, "|")) 277 { 278 DEBUG(2, ("%s: resolving use=%s (in core)", 279 child, lookfor)); 280 281 qp->uses[i].parent = rp; 282 foundit = TRUE; 283 } 284 285 /* if that didn't work, try to merge in a compiled entry */ 286 if (!foundit) 287 { 288 TERMTYPE thisterm; 289 char filename[PATH_MAX]; 290 291 if (_nc_read_entry(lookfor, filename, &thisterm) == 1) 292 { 293 DEBUG(2, ("%s: resolving use=%s (compiled)", 294 child, lookfor)); 295 296 rp = (ENTRY *)malloc(sizeof(ENTRY)); 297 if (rp == NULL) 298 _nc_err_abort("Out of memory"); 299 memcpy(&rp->tterm, &thisterm, sizeof(TERMTYPE)); 300 rp->nuses = 0; 301 rp->next = lastread; 302 lastread = rp; 303 304 qp->uses[i].parent = rp; 305 foundit = TRUE; 306 } 307 } 308 309 /* no good, mark this one unresolvable and complain */ 310 if (!foundit) 311 { 312 unresolved++; 313 total_unresolved++; 314 315 _nc_curr_line = lookline; 316 _nc_warning("resolution of use=%s failed", lookfor); 317 qp->uses[i].parent = (ENTRY *)NULL; 318 } 319 } 320 } 321 if (total_unresolved) 322 { 323 /* free entries read in off disk */ 324 _nc_free_entries(lastread); 325 return(FALSE); 326 } 327 328 DEBUG(2, ("NAME RESOLUTION COMPLETED OK")); 329 330 /* 331 * OK, at this point all (char *) references have been successfully 332 * replaced by (ENTRY *) pointers. Time to do the actual merges. 333 */ 334 do { 335 TERMTYPE merged; 336 337 keepgoing = FALSE; 338 339 for_entry_list(qp) 340 if (qp->nuses > 0) 341 { 342 DEBUG(2, ("%s: attempting merge", _nc_first_name(qp->tterm.term_names))); 343 /* 344 * If any of the use entries we're looking for is 345 * incomplete, punt. We'll catch this entry on a 346 * subsequent pass. 347 */ 348 for (i = 0; i < qp->nuses; i++) 349 if (((ENTRY *)qp->uses[i].parent)->nuses) 350 { 351 DEBUG(2, ("%s: use entry %d unresolved", 352 _nc_first_name(qp->tterm.term_names), i)); 353 goto incomplete; 354 } 355 356 /* 357 * First, make sure there's no garbage in the merge block. 358 * as a side effect, copy into the merged entry the name 359 * field and string table pointer. 360 */ 361 memcpy(&merged, &qp->tterm, sizeof(TERMTYPE)); 362 363 /* 364 * Now merge in each use entry in the proper 365 * (reverse) order. 366 */ 367 for (; qp->nuses; qp->nuses--) 368 _nc_merge_entry(&merged, 369 &((ENTRY *)qp->uses[qp->nuses-1].parent)->tterm); 370 371 /* 372 * Now merge in the original entry. 373 */ 374 _nc_merge_entry(&merged, &qp->tterm); 375 376 /* 377 * Replace the original entry with the merged one. 378 */ 379 memcpy(&qp->tterm, &merged, sizeof(TERMTYPE)); 380 381 /* 382 * We know every entry is resolvable because name resolution 383 * didn't bomb. So go back for another pass. 384 */ 385 /* FALLTHRU */ 386 incomplete: 387 keepgoing = TRUE; 388 } 389 } while 390 (keepgoing); 391 392 DEBUG(2, ("MERGES COMPLETED OK")); 393 394 /* 395 * The exit condition of the loop above is such that all entries 396 * must now be resolved. Now handle cancellations. In a resolved 397 * entry there should be no cancellation markers. 398 */ 399 for_entry_list(qp) 400 { 401 for (j = 0; j < BOOLCOUNT; j++) 402 if (qp->tterm.Booleans[j] == CANCELLED_BOOLEAN) 403 qp->tterm.Booleans[j] = FALSE; 404 for (j = 0; j < NUMCOUNT; j++) 405 if (qp->tterm.Numbers[j] == CANCELLED_NUMERIC) 406 qp->tterm.Numbers[j] = ABSENT_NUMERIC; 407 for (j = 0; j < STRCOUNT; j++) 408 if (qp->tterm.Strings[j] == CANCELLED_STRING) 409 qp->tterm.Strings[j] = ABSENT_STRING; 410 } 411 412 /* 413 * We'd like to free entries read in off disk at this point, but can't. 414 * The merge_entry() code doesn't copy the strings in the use entries, 415 * it just aliases them. If this ever changes, do a 416 * free_entries(lastread) here. 417 */ 418 419 DEBUG(2, ("RESOLUTION FINISHED")); 420 421 _nc_curr_col = -1; 422 for_entry_list(qp) 423 { 424 _nc_curr_line = qp->startline; 425 _nc_set_type(_nc_first_name(qp->tterm.term_names)); 426 sanity_check(&qp->tterm); 427 } 428 429 DEBUG(2, ("SANITY CHECK FINISHED")); 430 431 return(TRUE); 432 } 433 434 /* 435 * This bit of legerdemain turns all the terminfo variable names into 436 * references to locations in the arrays Booleans, Numbers, and Strings --- 437 * precisely what's needed. 438 */ 439 440 #undef CUR 441 #define CUR tp-> 442 443 /* 444 * Note that WANTED and PRESENT are not simple inverses! If a capability 445 * has been explicitly cancelled, it's not considered WANTED. 446 */ 447 #define WANTED(s) ((s) == ABSENT_STRING) 448 #define PRESENT(s) (((s) != ABSENT_STRING) && ((s) != CANCELLED_STRING)) 449 450 #define ANDMISSING(p,q) \ 451 {if (PRESENT(p) && !PRESENT(q)) _nc_warning(#p " but no " #q);} 452 453 #define PAIRED(p,q) \ 454 { \ 455 if (PRESENT(q) && !PRESENT(p)) \ 456 _nc_warning(#q " but no " #p); \ 457 if (PRESENT(p) && !PRESENT(q)) \ 458 _nc_warning(#p " but no " #q); \ 459 } 460 461 static void sanity_check(TERMTYPE *tp) 462 { 463 #ifdef __UNUSED__ /* this casts too wide a net */ 464 bool terminal_entry = !strchr(tp->term_names, '+'); 465 #endif 466 467 if (!PRESENT(exit_attribute_mode)) 468 { 469 #ifdef __UNUSED__ /* this casts too wide a net */ 470 if (terminal_entry && 471 (PRESENT(set_attributes) 472 || PRESENT(enter_standout_mode) 473 || PRESENT(enter_underline_mode) 474 || PRESENT(enter_blink_mode) 475 || PRESENT(enter_bold_mode) 476 || PRESENT(enter_dim_mode) 477 || PRESENT(enter_secure_mode) 478 || PRESENT(enter_protected_mode) 479 || PRESENT(enter_reverse_mode))) 480 _nc_warning("no exit_attribute_mode"); 481 #endif /* __UNUSED__ */ 482 PAIRED(enter_standout_mode, exit_standout_mode) 483 PAIRED(enter_underline_mode, exit_underline_mode) 484 } 485 486 /* listed in structure-member order of first argument */ 487 #ifdef __UNUSED__ 488 ANDMISSING(cursor_invisible, cursor_normal) 489 ANDMISSING(cursor_visible, cursor_normal) 490 #endif /* __UNUSED__ */ 491 PAIRED(enter_alt_charset_mode, exit_alt_charset_mode) 492 ANDMISSING(enter_alt_charset_mode, acs_chars) 493 ANDMISSING(exit_alt_charset_mode, acs_chars) 494 ANDMISSING(enter_blink_mode, exit_attribute_mode) 495 ANDMISSING(enter_bold_mode, exit_attribute_mode) 496 PAIRED(exit_ca_mode, enter_ca_mode) 497 PAIRED(enter_delete_mode, exit_delete_mode) 498 ANDMISSING(enter_dim_mode, exit_attribute_mode) 499 PAIRED(enter_insert_mode, exit_insert_mode) 500 ANDMISSING(enter_secure_mode, exit_attribute_mode) 501 ANDMISSING(enter_protected_mode, exit_attribute_mode) 502 ANDMISSING(enter_reverse_mode, exit_attribute_mode) 503 PAIRED(from_status_line, to_status_line) 504 PAIRED(meta_off, meta_on) 505 506 PAIRED(prtr_on, prtr_off) 507 PAIRED(save_cursor, restore_cursor) 508 PAIRED(enter_xon_mode, exit_xon_mode) 509 PAIRED(enter_am_mode, exit_am_mode) 510 ANDMISSING(label_off, label_on) 511 PAIRED(display_clock, remove_clock) 512 ANDMISSING(set_color_pair, initialize_pair) 513 514 /* Some checks that we should make, but don't want to confuse people 515 * with. Put those under the tic -v option so we can still get them. 516 */ 517 if (_nc_tracing) { 518 519 /* 520 * From XSI & O'Reilly, we gather that sc/rc are required if csr is 521 * given, because the cursor position after the scrolling operation is 522 * performed is undefined. 523 */ 524 ANDMISSING(change_scroll_region, save_cursor) 525 ANDMISSING(change_scroll_region, restore_cursor) 526 527 /* 528 * Some non-curses applications (e.g., jove) get confused if we have 529 * both ich/ich1 and smir/rmir. Let's be nice and warn about that, 530 * too, even though ncurses handles it. 531 */ 532 if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode)) 533 && (PRESENT(insert_character) || PRESENT(parm_ich))) { 534 _nc_warning("non-curses applications may be confused by ich/ich1 with smir/rmir"); 535 } 536 } 537 #undef PAIRED 538 #undef ANDMISSING 539 } 540