1% inputstack.w 2% 3% Copyright 2009-2010 Taco Hoekwater <taco@@luatex.org> 4% 5% This file is part of LuaTeX. 6% 7% LuaTeX is free software; you can redistribute it and/or modify it under 8% the terms of the GNU General Public License as published by the Free 9% Software Foundation; either version 2 of the License, or (at your 10% option) any later version. 11% 12% LuaTeX is distributed in the hope that it will be useful, but WITHOUT 13% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14% FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 15% License for more details. 16% 17% You should have received a copy of the GNU General Public License along 18% with LuaTeX; if not, see <http://www.gnu.org/licenses/>. 19 20@ @c 21 22 23#include "ptexlib.h" 24 25@ @c 26#define end_line_char int_par(end_line_char_code) 27#define error_context_lines int_par(error_context_lines_code) 28 29 30in_state_record *input_stack = NULL; 31int input_ptr = 0; /* first unused location of |input_stack| */ 32int max_in_stack = 0; /* largest value of |input_ptr| when pushing */ 33in_state_record cur_input; /* the ``top'' input state */ 34 35 36int in_open = 0; /* the number of lines in the buffer, less one */ 37int open_parens = 0; /* the number of open text files */ 38alpha_file *input_file = NULL; 39int line = 0; /* current line number in the current source file */ 40int *line_stack = NULL; 41str_number *source_filename_stack = NULL; 42char **full_source_filename_stack = NULL; 43 44 45int scanner_status = 0; /* can a subfile end now? */ 46pointer warning_index = null; /* identifier relevant to non-|normal| scanner status */ 47pointer def_ref = null; /* reference count of token list being defined */ 48 49 50 51@ Here is a procedure that uses |scanner_status| to print a warning message 52when a subfile has ended, and at certain other crucial times: 53 54@c 55void runaway(void) 56{ 57 pointer p = null; /* head of runaway list */ 58 if (scanner_status > skipping) { 59 switch (scanner_status) { 60 case defining: 61 tprint_nl("Runaway definition"); 62 p = def_ref; 63 break; 64 case matching: 65 tprint_nl("Runaway argument"); 66 p = temp_token_head; 67 break; 68 case aligning: 69 tprint_nl("Runaway preamble"); 70 p = hold_token_head; 71 break; 72 case absorbing: 73 tprint_nl("Runaway text"); 74 p = def_ref; 75 break; 76 default: /* there are no other cases */ 77 break; 78 } 79 print_char('?'); 80 print_ln(); 81 show_token_list(token_link(p), null, error_line - 10); 82 } 83} 84 85 86@ The |param_stack| is an auxiliary array used to hold pointers to the token 87lists for parameters at the current level and subsidiary levels of input. 88This stack is maintained with convention (2), and it grows at a different 89rate from the others. 90 91@c 92pointer *param_stack = NULL; /* token list pointers for parameters */ 93int param_ptr = 0; /* first unused entry in |param_stack| */ 94int max_param_stack = 0; /* largest value of |param_ptr|, will be |<=param_size+9| */ 95 96@ The input routines must also interact with the processing of 97\.{\\halign} and \.{\\valign}, since the appearance of tab marks and 98\.{\\cr} in certain places is supposed to trigger the beginning of special 99$v_j$ template text in the scanner. This magic is accomplished by an 100|align_state| variable that is increased by~1 when a `\.{\char'173}' is 101scanned and decreased by~1 when a `\.{\char'175}' is scanned. The |align_state| 102is nonzero during the $u_j$ template, after which it is set to zero; the 103$v_j$ template begins when a tab mark or \.{\\cr} occurs at a time that 104|align_state=0|. 105 106@c 107int align_state = 0; /* group level with respect to current alignment */ 108 109 110@ Thus, the ``current input state'' can be very complicated indeed; there 111can be many levels and each level can arise in a variety of ways. The 112|show_context| procedure, which is used by \TeX's error-reporting routine to 113print out the current input state on all levels down to the most recent 114line of characters from an input file, illustrates most of these conventions. 115The global variable |base_ptr| contains the lowest level that was 116displayed by this procedure. 117 118@c 119int base_ptr = 0; /* shallowest level shown by |show_context| */ 120 121 122@ The status at each level is indicated by printing two lines, where the first 123line indicates what was read so far and the second line shows what remains 124to be read. The context is cropped, if necessary, so that the first line 125contains at most |half_error_line| characters, and the second contains 126at most |error_line|. Non-current input levels whose |token_type| is 127`|backed_up|' are shown only if they have not been fully read. 128 129@c 130static void print_token_list_type(int t) 131{ 132 switch (t) { 133 case parameter: 134 tprint_nl("<argument> "); 135 break; 136 case u_template: 137 case v_template: 138 tprint_nl("<template> "); 139 break; 140 case backed_up: 141 if (iloc == null) 142 tprint_nl("<recently read> "); 143 else 144 tprint_nl("<to be read again> "); 145 break; 146 case inserted: 147 tprint_nl("<inserted text> "); 148 break; 149 case macro: 150 print_ln(); 151 print_cs(iname); 152 break; 153 case output_text: 154 tprint_nl("<output> "); 155 break; 156 case every_par_text: 157 tprint_nl("<everypar> "); 158 break; 159 case every_math_text: 160 tprint_nl("<everymath> "); 161 break; 162 case every_display_text: 163 tprint_nl("<everydisplay> "); 164 break; 165 case every_hbox_text: 166 tprint_nl("<everyhbox> "); 167 break; 168 case every_vbox_text: 169 tprint_nl("<everyvbox> "); 170 break; 171 case every_job_text: 172 tprint_nl("<everyjob> "); 173 break; 174 case every_cr_text: 175 tprint_nl("<everycr> "); 176 break; 177 case mark_text: 178 tprint_nl("<mark> "); 179 break; 180 case every_eof_text: 181 tprint_nl("<everyeof> "); 182 break; 183 case write_text: 184 tprint_nl("<write> "); 185 break; 186 default: 187 tprint_nl("?"); 188 break; /* this should never happen */ 189 } 190} 191 192 193@ Here it is necessary to explain a little trick. We don't want to store a long 194string that corresponds to a token list, because that string might take up 195lots of memory; and we are printing during a time when an error message is 196being given, so we dare not do anything that might overflow one of \TeX's 197tables. So `pseudoprinting' is the answer: We enter a mode of printing 198that stores characters into a buffer of length |error_line|, where character 199$k+1$ is placed into \hbox{|trick_buf[k mod error_line]|} if 200|k<trick_count|, otherwise character |k| is dropped. Initially we set 201|tally:=0| and |trick_count:=1000000|; then when we reach the 202point where transition from line 1 to line 2 should occur, we 203set |first_count:=tally| and |trick_count:=@tmax@>(error_line, 204tally+1+error_line-half_error_line)|. At the end of the 205pseudoprinting, the values of |first_count|, |tally|, and 206|trick_count| give us all the information we need to print the two lines, 207and all of the necessary text is in |trick_buf|. 208 209Namely, let |l| be the length of the descriptive information that appears 210on the first line. The length of the context information gathered for that 211line is |k=first_count|, and the length of the context information 212gathered for line~2 is $m=\min(|tally|, |trick_count|)-k$. If |l+k<=h|, 213where |h=half_error_line|, we print |trick_buf[0..k-1]| after the 214descriptive information on line~1, and set |n:=l+k|; here |n| is the 215length of line~1. If $l+k>h$, some cropping is necessary, so we set |n:=h| 216and print `\.{...}' followed by 217$$\hbox{|trick_buf[(l+k-h+3)..k-1]|,}$$ 218where subscripts of |trick_buf| are circular modulo |error_line|. The 219second line consists of |n|~spaces followed by |trick_buf[k..(k+m-1)]|, 220unless |n+m>error_line|; in the latter case, further cropping is done. 221This is easier to program than to explain. 222 223 224 225@ The following code sets up the print routines so that they will gather 226the desired information. 227 228@c 229void set_trick_count(void) 230{ 231 first_count = tally; 232 trick_count = tally + 1 + error_line - half_error_line; 233 if (trick_count < error_line) 234 trick_count = error_line; 235} 236 237#define begin_pseudoprint() do { \ 238 l=tally; tally=0; selector=pseudo; \ 239 trick_count=1000000; \ 240 } while (0) 241 242#define PSEUDO_PRINT_THE_LINE() do { \ 243 begin_pseudoprint(); \ 244 if (buffer[ilimit]==end_line_char) j=ilimit; \ 245 else j=ilimit+1; /* determine the effective end of the line */ \ 246 if (j>0) { \ 247 for (i=istart;i<=j-1;i++) { \ 248 if (i==iloc) set_trick_count(); \ 249 print_char(buffer[i]); \ 250 } \ 251 } \ 252 } while (0) 253 254@ @c 255void show_context(void) 256{ /* prints where the scanner is */ 257 int old_setting; /* saved |selector| setting */ 258 int nn; /* number of contexts shown so far, less one */ 259 boolean bottom_line; /* have we reached the final context to be shown? */ 260 int i; /* index into |buffer| */ 261 int j; /* end of current line in |buffer| */ 262 int l; /* length of descriptive information on line 1 */ 263 int m; /* context information gathered for line 2 */ 264 int n; /* length of line 1 */ 265 int p; /* starting or ending place in |trick_buf| */ 266 int q; /* temporary index */ 267 268 base_ptr = input_ptr; 269 input_stack[base_ptr] = cur_input; 270 /* store current state */ 271 nn = -1; 272 bottom_line = false; 273 while (true) { 274 cur_input = input_stack[base_ptr]; /* enter into the context */ 275 if (istate != token_list) { 276 if ((iname > 21) || (base_ptr == 0)) 277 bottom_line = true; 278 } 279 if ((base_ptr == input_ptr) || bottom_line 280 || (nn < error_context_lines)) { 281 /* Display the current context */ 282 if ((base_ptr == input_ptr) || (istate != token_list) || 283 (token_type != backed_up) || (iloc != null)) { 284 /* we omit backed-up token lists that have already been read */ 285 tally = 0; /* get ready to count characters */ 286 old_setting = selector; 287 if (istate != token_list) { 288 /* Print location of current line */ 289 /* 290 This routine should be changed, if necessary, to give the best possible 291 indication of where the current line resides in the input file. 292 For example, on some systems it is best to print both a page and line number. 293 */ 294 if (iname <= 17) { 295 if (terminal_input) { 296 if (base_ptr == 0) 297 tprint_nl("<*>"); 298 else 299 tprint_nl("<insert> "); 300 } else { 301 tprint_nl("<read "); 302 if (iname == 17) 303 print_char('*'); 304 else 305 print_int(iname - 1); 306 print_char('>'); 307 }; 308 } else { 309 tprint_nl("l."); 310 if (iindex == in_open) { 311 print_int(line); 312 } else { /* input from a pseudo file */ 313 print_int(line_stack[iindex + 1]); 314 } 315 } 316 print_char(' '); 317 PSEUDO_PRINT_THE_LINE(); 318 } else { 319 print_token_list_type(token_type); 320 321 begin_pseudoprint(); 322 if (token_type < macro) 323 show_token_list(istart, iloc, 100000); 324 else 325 show_token_list(token_link(istart), iloc, 100000); /* avoid reference count */ 326 } 327 selector = old_setting; /* stop pseudoprinting */ 328 /* Print two lines using the tricky pseudoprinted information */ 329 if (trick_count == 1000000) 330 set_trick_count(); 331 /* |set_trick_count| must be performed */ 332 if (tally < trick_count) 333 m = tally - first_count; 334 else 335 m = trick_count - first_count; /* context on line 2 */ 336 if (l + first_count <= half_error_line) { 337 p = 0; 338 n = l + first_count; 339 } else { 340 tprint("..."); 341 p = l + first_count - half_error_line + 3; 342 n = half_error_line; 343 } 344 for (q = p; q <= first_count - 1; q++) 345 print_char(trick_buf[(q % error_line)]); 346 print_ln(); 347 for (q = 1; q <= n; q++) 348 print_char(' '); /* print |n| spaces to begin line~2 */ 349 if (m + n <= error_line) 350 p = first_count + m; 351 else 352 p = first_count + (error_line - n - 3); 353 for (q = first_count; q <= p - 1; q++) 354 print_char(trick_buf[(q % error_line)]); 355 if (m + n > error_line) 356 tprint("..."); 357 358 incr(nn); 359 } 360 } else if (nn == error_context_lines) { 361 tprint_nl("..."); 362 incr(nn); /* omitted if |error_context_lines<0| */ 363 } 364 if (bottom_line) 365 break; 366 decr(base_ptr); 367 } 368 cur_input = input_stack[input_ptr]; /* restore original state */ 369} 370 371 372@ The following subroutines change the input status in commonly needed ways. 373 374First comes |push_input|, which stores the current state and creates a 375new level (having, initially, the same properties as the old). 376 377@c 378/* enter a new input level, save the old */ 379 380void push_input(void) 381{ 382 if (input_ptr > max_in_stack) { 383 max_in_stack = input_ptr; 384 if (input_ptr == stack_size) 385 overflow("input stack size", (unsigned) stack_size); 386 } 387 input_stack[input_ptr] = cur_input; /* stack the record */ 388 nofilter = false; 389 incr(input_ptr); 390} 391 392@ 393Here is a procedure that starts a new level of token-list input, given 394a token list |p| and its type |t|. If |t=macro|, the calling routine should 395set |name| and |loc|. 396 397@c 398void begin_token_list(halfword p, quarterword t) 399{ 400 push_input(); 401 istate = token_list; 402 istart = p; 403 token_type = (unsigned char) t; 404 if (t >= macro) { /* the token list starts with a reference count */ 405 add_token_ref(p); 406 if (t == macro) { 407 param_start = param_ptr; 408 } else { 409 iloc = token_link(p); 410 if (int_par(tracing_macros_code) > 1) { 411 begin_diagnostic(); 412 tprint_nl(""); 413 if (t == mark_text) 414 tprint_esc("mark"); 415 else if (t == write_text) 416 tprint_esc("write"); 417 else 418 print_cmd_chr(assign_toks_cmd, 419 t - output_text + output_routine_loc); 420 tprint("->"); 421 token_show(p); 422 end_diagnostic(false); 423 } 424 } 425 } else { 426 iloc = p; 427 } 428} 429 430 431@ When a token list has been fully scanned, the following computations 432should be done as we leave that level of input. The |token_type| tends 433to be equal to either |backed_up| or |inserted| about 2/3 of the time. 434@^inner loop@> 435 436@c 437void end_token_list(void) 438{ /* leave a token-list input level */ 439 if (token_type >= backed_up) { /* token list to be deleted */ 440 if (token_type <= inserted) { 441 flush_list(istart); 442 } else { 443 delete_token_ref(istart); /* update reference count */ 444 if (token_type == macro) { /* parameters must be flushed */ 445 while (param_ptr > param_start) { 446 decr(param_ptr); 447 flush_list(param_stack[param_ptr]); 448 } 449 } 450 } 451 } else if (token_type == u_template) { 452 if (align_state > 500000) 453 align_state = 0; 454 else 455 fatal_error("(interwoven alignment preambles are not allowed)"); 456 } 457 pop_input(); 458 check_interrupt(); 459} 460 461 462@ Sometimes \TeX\ has read too far and wants to ``unscan'' what it has 463seen. The |back_input| procedure takes care of this by putting the token 464just scanned back into the input stream, ready to be read again. This 465procedure can be used only if |cur_tok| represents the token to be 466replaced. Some applications of \TeX\ use this procedure a lot, 467so it has been slightly optimized for speed. 468@^inner loop@> 469 470@c 471void back_input(void) 472{ /* undoes one token of input */ 473 halfword p; /* a token list of length one */ 474 while ((istate == token_list) && (iloc == null) 475 && (token_type != v_template)) 476 end_token_list(); /* conserve stack space */ 477 p = get_avail(); 478 set_token_info(p, cur_tok); 479 if (cur_tok < right_brace_limit) { 480 if (cur_tok < left_brace_limit) 481 decr(align_state); 482 else 483 incr(align_state); 484 } 485 push_input(); 486 istate = token_list; 487 istart = p; 488 token_type = backed_up; 489 iloc = p; /* that was |back_list(p)|, without procedure overhead */ 490} 491 492@ Insert token |p| into \TeX's input 493@c 494int reinsert_token(boolean a, halfword pp) 495{ 496 halfword t; 497 t = cur_tok; 498 cur_tok = pp; 499 if (a) { 500 halfword p; 501 p = get_avail(); 502 set_token_info(p, cur_tok); 503 set_token_link(p, iloc); 504 iloc = p; 505 istart = p; 506 if (cur_tok < right_brace_limit) { 507 if (cur_tok < left_brace_limit) 508 decr(align_state); 509 else 510 incr(align_state); 511 } 512 } else { 513 back_input(); 514 a = true; /* etex is always on */ 515 } 516 cur_tok = t; 517 return a; 518} 519 520 521@ The |begin_file_reading| procedure starts a new level of input for lines 522of characters to be read from a file, or as an insertion from the 523terminal. It does not take care of opening the file, nor does it set |loc| 524or |limit| or |line|. 525@^system dependencies@> 526 527@c 528void begin_file_reading(void) 529{ 530 if (in_open == max_in_open) 531 overflow("text input levels", (unsigned) max_in_open); 532 if (first == buf_size) 533 check_buffer_overflow(first); 534 incr(in_open); 535 push_input(); 536 iindex = (unsigned char) in_open; 537 source_filename_stack[iindex] = 0; 538 full_source_filename_stack[iindex] = NULL; 539 eof_seen[iindex] = false; 540 grp_stack[iindex] = cur_boundary; 541 if_stack[iindex] = cond_ptr; 542 line_stack[iindex] = line; 543 istart = first; 544 istate = mid_line; 545 iname = 0; /* |terminal_input| is now |true| */ 546 line_catcode_table = DEFAULT_CAT_TABLE; 547 line_partial = false; 548 /* Prepare terminal input {\sl Sync\TeX} information */ 549 synctex_tag = 0; 550} 551 552 553@ Conversely, the variables must be downdated when such a level of input 554is finished: 555 556@c 557void end_file_reading(void) 558{ 559 first = istart; 560 line = line_stack[iindex]; 561 if ((iname >= 18) && (iname <= 20)) 562 pseudo_close(); 563 else if (iname == 21) 564 luacstring_close(iindex); 565 else if (iname > 17) 566 lua_a_close_in(cur_file, 0); /* forget it */ 567 pop_input(); 568 decr(in_open); 569} 570 571 572@ In order to keep the stack from overflowing during a long sequence of 573inserted `\.{\\show}' commands, the following routine removes completed 574error-inserted lines from memory. 575 576@c 577void clear_for_error_prompt(void) 578{ 579 while ((istate != token_list) && terminal_input 580 && (input_ptr > 0) && (iloc > ilimit)) 581 end_file_reading(); 582 print_ln(); 583 clear_terminal(); 584} 585 586@ To get \TeX's whole input mechanism going, we perform the following 587 actions. 588 589@c 590void initialize_inputstack(void) 591{ 592 input_ptr = 0; 593 max_in_stack = 0; 594 source_filename_stack[0] = 0; 595 596 full_source_filename_stack[0] = NULL; 597 in_open = 0; 598 open_parens = 0; 599 max_buf_stack = 0; 600 601 grp_stack[0] = 0; 602 if_stack[0] = null; 603 param_ptr = 0; 604 max_param_stack = 0; 605 first = buf_size; 606 do { 607 buffer[first] = 0; 608 decr(first); 609 } while (first != 0); 610 scanner_status = normal; 611 warning_index = null; 612 first = 1; 613 istate = new_line; 614 istart = 1; 615 iindex = 0; 616 line = 0; 617 iname = 0; 618 nofilter = false; 619 force_eof = false; 620 luacstrings = 0; 621 line_catcode_table = DEFAULT_CAT_TABLE; 622 line_partial = false; 623 align_state = 1000000; 624 if (!init_terminal()) 625 exit(EXIT_FAILURE); /* |goto final_end|; */ 626 ilimit = last; 627 first = last + 1; /* |init_terminal| has set |loc| and |last| */ 628} 629 630 631 632 633@ The global variable |pseudo_files| is used to maintain a stack of 634pseudo files. The |pseudo_lines| field of each pseudo file points to 635a linked list of variable size nodes representing lines not yet 636processed: the |subtype| field contains the size of this node, 637all the following words contain ASCII codes. 638 639@c 640halfword pseudo_files; /* stack of pseudo files */ 641 642static halfword string_to_pseudo(str_number str, int nl) 643{ 644 halfword i, r, q = null; 645 unsigned l, len; 646 four_quarters w; 647 int sz; 648 halfword h = new_node(pseudo_file_node, 0); 649 unsigned char *s = str_string(str); 650 len = (unsigned) str_length(str); 651 l = 0; 652 while (l < len) { 653 unsigned m = l; /* start of current line */ 654 while ((l < len) && (s[l] != nl)) 655 l++; 656 sz = (int) (l - m + 7) / 4; 657 if (sz == 1) 658 sz = 2; 659 r = new_node(pseudo_line_node, sz); 660 i = r; 661 while (--sz > 1) { 662 w.b0 = s[m++]; 663 w.b1 = s[m++]; 664 w.b2 = s[m++]; 665 w.b3 = s[m++]; 666 varmem[++i].qqqq = w; 667 } 668 w.b0 = (quarterword) (l > m ? s[m++] : ' '); 669 w.b1 = (quarterword) (l > m ? s[m++] : ' '); 670 w.b2 = (quarterword) (l > m ? s[m++] : ' '); 671 w.b3 = (quarterword) (l > m ? s[m] : ' '); 672 varmem[++i].qqqq = w; 673 if (pseudo_lines(h) == null) { 674 pseudo_lines(h) = r; 675 q = r; 676 } else { 677 couple_nodes(q, r); 678 } 679 q = vlink(q); 680 if (s[l] == nl) 681 l++; 682 } 683 return h; 684} 685 686 687@ The |pseudo_start| procedure initiates reading from a pseudo file. 688 689@c 690void pseudo_from_string(void) 691{ 692 str_number s; /* string to be converted into a pseudo file */ 693 halfword p; /* for list construction */ 694 s = make_string(); 695 /* Convert string |s| into a new pseudo file */ 696 p = string_to_pseudo(s, int_par(new_line_char_code)); 697 vlink(p) = pseudo_files; 698 pseudo_files = p; 699 flush_str(s); 700 /* Initiate input from new pseudo file */ 701 begin_file_reading(); /* set up |cur_file| and new level of input */ 702 line = 0; 703 ilimit = istart; 704 iloc = ilimit + 1; /* force line read */ 705 if (int_par(tracing_scan_tokens_code) > 0) { 706 if (term_offset > max_print_line - 3) 707 print_ln(); 708 else if ((term_offset > 0) || (file_offset > 0)) 709 print_char(' '); 710 iname = 20; 711 tprint("( "); 712 incr(open_parens); 713 update_terminal(); 714 } else { 715 iname = 18; 716 } 717 /* Prepare pseudo file {\sl Sync\TeX} information */ 718 synctex_tag = 0; 719} 720 721void pseudo_start(void) 722{ 723 int old_setting; /* holds |selector| setting */ 724 scan_general_text(); 725 old_setting = selector; 726 selector = new_string; 727 token_show(temp_token_head); 728 selector = old_setting; 729 flush_list(token_link(temp_token_head)); 730 str_room(1); 731 pseudo_from_string(); 732} 733 734@ @c 735void lua_string_start(void) 736{ 737 begin_file_reading(); /* set up |cur_file| and new level of input */ 738 line = 0; 739 ilimit = istart; 740 iloc = ilimit + 1; /* force line read */ 741 iname = 21; 742 luacstring_start(iindex); 743} 744 745@ Here we read a line from the current pseudo file into |buffer|. 746 747@c 748boolean pseudo_input(void) 749{ /* inputs the next line or returns |false| */ 750 halfword p; /* current line from pseudo file */ 751 int sz; /* size of node |p| */ 752 four_quarters w; /* four ASCII codes */ 753 halfword r; /* loop index */ 754 last = first; /* cf.\ Matthew 19\thinspace:\thinspace30 */ 755 p = pseudo_lines(pseudo_files); 756 if (p == null) { 757 return false; 758 } else { 759 pseudo_lines(pseudo_files) = vlink(p); 760 sz = subtype(p); 761 if (4 * sz - 3 >= buf_size - last) 762 check_buffer_overflow(last + 4 * sz); 763 last = first; 764 for (r = p + 1; r <= p + sz - 1; r++) { 765 w = varmem[r].qqqq; 766 buffer[last] = (packed_ASCII_code) w.b0; 767 buffer[last + 1] = (packed_ASCII_code) w.b1; 768 buffer[last + 2] = (packed_ASCII_code) w.b2; 769 buffer[last + 3] = (packed_ASCII_code) w.b3; 770 last += 4; 771 } 772 if (last >= max_buf_stack) 773 max_buf_stack = last + 1; 774 while ((last > first) && (buffer[last - 1] == ' ')) 775 decr(last); 776 flush_node(p); 777 } 778 return true; 779} 780 781@ When we are done with a pseudo file we `close' it. 782 783@c 784void pseudo_close(void) 785{ /* close the top level pseudo file */ 786 halfword p; 787 p = vlink(pseudo_files); 788 flush_node(pseudo_files); 789 pseudo_files = p; 790} 791