|
|
1.1 ! root 1: /*************************************************************************** ! 2: * This program is Copyright (C) 1986, 1987, 1988 by Jonathan Payne. JOVE * ! 3: * is provided to you without charge, and with no warranty. You may give * ! 4: * away copies of JOVE, including sources, provided that this notice is * ! 5: * included in all the files. * ! 6: ***************************************************************************/ ! 7: ! 8: #include "jove.h" ! 9: #include "ctype.h" ! 10: #include "list.h" ! 11: #include "chars.h" ! 12: #include "disp.h" ! 13: ! 14: private int ! 15: newchunk proto((void)); ! 16: private void ! 17: DoNewline proto((int indentp)), ! 18: init_specials proto((void)); ! 19: ! 20: /* Make a newline after AFTER in buffer BUF, UNLESS after is 0, ! 21: in which case we insert the newline before after. */ ! 22: ! 23: Line * ! 24: listput(buf, after) ! 25: register Buffer *buf; ! 26: register Line *after; ! 27: { ! 28: register Line *newline = nbufline(); ! 29: ! 30: if (after == 0) { /* Before the first line */ ! 31: newline->l_next = buf->b_first; ! 32: newline->l_prev = 0; ! 33: buf->b_first = newline; ! 34: } else { ! 35: newline->l_prev = after; ! 36: newline->l_next = after->l_next; ! 37: after->l_next = newline; ! 38: } ! 39: if (newline->l_next) ! 40: newline->l_next->l_prev = newline; ! 41: else ! 42: if (buf) ! 43: buf->b_last = newline; ! 44: if (buf && buf->b_dot == 0) ! 45: buf->b_dot = newline; ! 46: return newline; ! 47: } ! 48: ! 49: /* Divide the current line and move the current line to the next one */ ! 50: ! 51: void ! 52: LineInsert(num) ! 53: register int num; ! 54: { ! 55: char newline[LBSIZE]; ! 56: register Line *newdot, ! 57: *olddot; ! 58: int oldchar; ! 59: ! 60: olddot = curline; ! 61: oldchar = curchar; ! 62: ! 63: newdot = curline; ! 64: while (--num >= 0) { ! 65: newdot = listput(curbuf, newdot); ! 66: SavLine(newdot, NullStr); ! 67: } ! 68: ! 69: modify(); ! 70: if (curchar != 0) { ! 71: strcpy(newline, &linebuf[curchar]); ! 72: linebuf[curchar] = '\0'; /* Shorten this line */ ! 73: SavLine(curline, linebuf); ! 74: strcpy(linebuf, newline); ! 75: } else { /* Redisplay optimization */ ! 76: newdot->l_dline = curline->l_dline; ! 77: SavLine(curline, NullStr); ! 78: } ! 79: ! 80: makedirty(curline); ! 81: curline = newdot; ! 82: curchar = 0; ! 83: makedirty(curline); ! 84: IFixMarks(olddot, oldchar, curline, curchar); ! 85: } ! 86: ! 87: /* Inserts tabs and spaces to move the cursor to column GOAL. It ! 88: Uses the most optimal number of tabs and spaces no matter what ! 89: was there before hand. */ ! 90: ! 91: void ! 92: n_indent(goal) ! 93: register int goal; ! 94: { ! 95: int dotcol, ! 96: incrmt; ! 97: ! 98: DelWtSpace(); ! 99: dotcol = calc_pos(linebuf, curchar); ! 100: ! 101: for (;;) { ! 102: incrmt = (tabstop - (dotcol % tabstop)); ! 103: if (dotcol + incrmt > goal) ! 104: break; ! 105: insert_c('\t', 1); ! 106: dotcol += incrmt; ! 107: } ! 108: if (dotcol != goal) ! 109: insert_c(' ', (goal - dotcol)); ! 110: } ! 111: ! 112: #ifdef ABBREV ! 113: void ! 114: MaybeAbbrevExpand() ! 115: { ! 116: if (MinorMode(Abbrev) && !ismword(LastKeyStruck) && ! 117: !bolp() && ismword(linebuf[curchar - 1])) ! 118: AbbrevExpand(); ! 119: } ! 120: #endif ! 121: ! 122: void ! 123: SelfInsert() ! 124: { ! 125: #ifdef ABBREV ! 126: MaybeAbbrevExpand(); ! 127: #endif ! 128: if (LastKeyStruck != CTL('J') && MinorMode(OverWrite)) { ! 129: register int num, ! 130: i; ! 131: ! 132: for (i = 0, num = arg_value(); i < num; i++) { ! 133: int pos = calc_pos(linebuf, curchar); ! 134: ! 135: if (!eolp()) { ! 136: if (linebuf[curchar] == '\t') { ! 137: if ((pos + 1) == ((pos + tabstop) - (pos % tabstop))) ! 138: del_char(FORWARD, 1, NO); ! 139: } else ! 140: del_char(FORWARD, 1, NO); ! 141: } ! 142: insert_c(LastKeyStruck, 1); ! 143: } ! 144: } else ! 145: Insert(LastKeyStruck); ! 146: ! 147: if (MinorMode(Fill) && (curchar >= RMargin || ! 148: (calc_pos(linebuf, curchar) >= RMargin))) { ! 149: int margin; ! 150: Bufpos save; ! 151: ! 152: if (MinorMode(Indent)) { ! 153: DOTsave(&save); ! 154: ToIndent(); ! 155: margin = calc_pos(linebuf, curchar); ! 156: SetDot(&save); ! 157: } else ! 158: margin = LMargin; ! 159: DoJustify(curline, 0, curline, ! 160: curchar + (int)strlen(&linebuf[curchar]), 1, margin); ! 161: } ! 162: } ! 163: ! 164: void ! 165: Insert(c) ! 166: int c; ! 167: { ! 168: if (c == CTL('J')) ! 169: LineInsert(arg_value()); ! 170: else ! 171: insert_c(c, arg_value()); ! 172: } ! 173: ! 174: /* insert character C N times at point */ ! 175: void ! 176: insert_c(c, n) ! 177: int c, ! 178: n; ! 179: { ! 180: if (n <= 0) ! 181: return; ! 182: modify(); ! 183: makedirty(curline); ! 184: ins_c(c, linebuf, curchar, n, LBSIZE); ! 185: IFixMarks(curline, curchar, curline, curchar + n); ! 186: curchar += n; ! 187: } ! 188: ! 189: /* Tab in to the right place for C mode */ ! 190: ! 191: void ! 192: Tab() ! 193: { ! 194: #ifdef LISP ! 195: if (MajorMode(LISPMODE) && (bolp() || !eolp())) { ! 196: int dotchar = curchar; ! 197: Mark *m = 0; ! 198: ! 199: ToIndent(); ! 200: if (dotchar > curchar) ! 201: m = MakeMark(curline, dotchar, M_FLOATER); ! 202: (void) lisp_indent(); ! 203: if (m) { ! 204: ToMark(m); ! 205: DelMark(m); ! 206: } else ! 207: ToIndent(); ! 208: return; ! 209: } ! 210: #endif ! 211: if (MajorMode(CMODE)) { ! 212: if (within_indent()) ! 213: (void) c_indent(NO); ! 214: else { ! 215: int curpos, ! 216: tabbed_pos; ! 217: ! 218: skip_wht_space(); ! 219: curpos = calc_pos(linebuf, curchar); ! 220: tabbed_pos = curpos + (CIndIncrmt - (curpos % CIndIncrmt)); ! 221: n_indent(tabbed_pos); ! 222: } ! 223: } else ! 224: SelfInsert(); ! 225: } ! 226: ! 227: void ! 228: QuotChar() ! 229: { ! 230: int c, ! 231: slow = NO; ! 232: ! 233: c = waitchar(&slow); ! 234: if (c != CTL('@')) ! 235: Insert(c); ! 236: } ! 237: ! 238: /* Insert the paren. If in C mode and c is a '}' then insert the ! 239: '}' in the "right" place for C indentation; that is indented ! 240: the same amount as the matching '{' is indented. */ ! 241: ! 242: int PDelay = 5, /* 1/2 a second */ ! 243: CIndIncrmt = 8; ! 244: ! 245: void ! 246: DoParen() ! 247: { ! 248: Bufpos *bp; ! 249: int tried = NO, ! 250: nx, ! 251: c = LastKeyStruck; ! 252: ! 253: if (!isclosep(c)) { ! 254: SelfInsert(); ! 255: return; ! 256: } ! 257: ! 258: if (MajorMode(CMODE) && c == '}' && within_indent()) { ! 259: bp = c_indent(YES); ! 260: tried = TRUE; ! 261: } ! 262: #ifdef LISP ! 263: if (MajorMode(LISPMODE) && c == ')' && blnkp(linebuf)) { ! 264: bp = lisp_indent(); ! 265: tried = TRUE; ! 266: } ! 267: #endif ! 268: SelfInsert(); ! 269: #ifdef MAC ! 270: if (MinorMode(ShowMatch) && !in_macro()) { ! 271: #else ! 272: if (MinorMode(ShowMatch) && !charp() && !in_macro()) { ! 273: #endif ! 274: b_char(1); /* Back onto the ')' */ ! 275: if (!tried) ! 276: bp = m_paren(c, BACKWARD, NO, YES); ! 277: f_char(1); ! 278: if (bp != 0) { ! 279: nx = in_window(curwind, bp->p_line); ! 280: if (nx != -1) { /* is visible */ ! 281: Bufpos b; ! 282: ! 283: DOTsave(&b); ! 284: SetDot(bp); ! 285: SitFor(PDelay); ! 286: SetDot(&b); ! 287: } else ! 288: s_mess("%s", lcontents(bp->p_line)); ! 289: } ! 290: mp_error(); /* display error message */ ! 291: } ! 292: } ! 293: ! 294: void ! 295: LineAI() ! 296: { ! 297: DoNewline(TRUE); ! 298: } ! 299: ! 300: void ! 301: Newline() ! 302: { ! 303: DoNewline(MinorMode(Indent)); ! 304: } ! 305: ! 306: private void ! 307: DoNewline(indentp) ! 308: int indentp; ! 309: { ! 310: Bufpos save; ! 311: int indent; ! 312: ! 313: /* first we calculate the indent of the current line */ ! 314: DOTsave(&save); ! 315: ToIndent(); ! 316: indent = calc_pos(linebuf, curchar); ! 317: SetDot(&save); ! 318: ! 319: #ifdef ABBREV ! 320: MaybeAbbrevExpand(); ! 321: #endif ! 322: #ifdef LISP ! 323: if (MajorMode(LISPMODE)) ! 324: DelWtSpace(); ! 325: else ! 326: #endif ! 327: if (indentp || blnkp(linebuf)) ! 328: DelWtSpace(); ! 329: ! 330: /* If there is more than 2 blank lines in a row then don't make ! 331: a newline, just move down one. */ ! 332: if (arg_value() == 1 && eolp() && TwoBlank()) ! 333: SetLine(curline->l_next); ! 334: else ! 335: LineInsert(arg_value()); ! 336: ! 337: if (indentp) ! 338: #ifdef LISP ! 339: if (MajorMode(LISPMODE)) ! 340: (void) lisp_indent(); ! 341: else ! 342: #endif ! 343: { ! 344: Bol(); ! 345: n_indent((LMargin == 0) ? indent : LMargin); ! 346: } ! 347: } ! 348: ! 349: void ! 350: ins_str(str, ok_nl) ! 351: register char *str; ! 352: int ok_nl; ! 353: { ! 354: register char c; ! 355: Bufpos save; ! 356: int llen; ! 357: ! 358: if (*str == 0) ! 359: return; /* ain't nothing to insert! */ ! 360: DOTsave(&save); ! 361: llen = strlen(linebuf); ! 362: while ((c = *str++) != '\0') { ! 363: if (c == '\n' || (ok_nl && llen >= LBSIZE - 2)) { ! 364: IFixMarks(save.p_line, save.p_char, curline, curchar); ! 365: modify(); ! 366: makedirty(curline); ! 367: LineInsert(1); ! 368: DOTsave(&save); ! 369: llen = strlen(linebuf); ! 370: } ! 371: if (c != '\n') { ! 372: ins_c(c, linebuf, curchar++, 1, LBSIZE); ! 373: llen += 1; ! 374: } ! 375: } ! 376: IFixMarks(save.p_line, save.p_char, curline, curchar); ! 377: modify(); ! 378: makedirty(curline); ! 379: } ! 380: ! 381: void ! 382: open_lines(n) ! 383: int n; ! 384: { ! 385: Bufpos dot; ! 386: ! 387: DOTsave(&dot); ! 388: LineInsert(n); /* Open the lines... */ ! 389: SetDot(&dot); ! 390: } ! 391: ! 392: void ! 393: OpenLine() ! 394: { ! 395: open_lines(arg_value()); ! 396: } ! 397: ! 398: /* Take the region FLINE/FCHAR to TLINE/TCHAR and insert it at ! 399: ATLINE/ATCHAR in WHATBUF. */ ! 400: ! 401: Bufpos * ! 402: DoYank(fline, fchar, tline, tchar, atline, atchar, whatbuf) ! 403: Line *fline, ! 404: *tline, ! 405: *atline; ! 406: int fchar, ! 407: tchar, ! 408: atchar; ! 409: Buffer *whatbuf; ! 410: { ! 411: register Line *newline; ! 412: static Bufpos bp; ! 413: char save[LBSIZE], ! 414: buf[LBSIZE]; ! 415: Line *startline = atline; ! 416: int startchar = atchar; ! 417: ! 418: lsave(); ! 419: if (whatbuf) ! 420: modify(); ! 421: (void) ltobuf(atline, genbuf); ! 422: strcpy(save, &genbuf[atchar]); ! 423: ! 424: (void) ltobuf(fline, buf); ! 425: if (fline == tline) ! 426: buf[tchar] = '\0'; ! 427: ! 428: linecopy(genbuf, atchar, &buf[fchar]); ! 429: atline->l_dline = putline(genbuf); ! 430: makedirty(atline); ! 431: ! 432: fline = fline->l_next; ! 433: while (fline != tline->l_next) { ! 434: newline = listput(whatbuf, atline); ! 435: newline->l_dline = fline->l_dline; ! 436: makedirty(newline); ! 437: fline = fline->l_next; ! 438: atline = newline; ! 439: atchar = 0; ! 440: } ! 441: ! 442: getline(atline->l_dline, genbuf); ! 443: atchar += tchar; ! 444: linecopy(genbuf, atchar, save); ! 445: atline->l_dline = putline(genbuf); ! 446: makedirty(atline); ! 447: IFixMarks(startline, startchar, atline, atchar); ! 448: bp.p_line = atline; ! 449: bp.p_char = atchar; ! 450: this_cmd = YANKCMD; ! 451: getDOT(); /* Whatever used to be in linebuf */ ! 452: return &bp; ! 453: } ! 454: ! 455: void ! 456: YankPop() ! 457: { ! 458: Line *line, ! 459: *last; ! 460: Mark *mp = CurMark(); ! 461: Bufpos *dot; ! 462: int dir = -1; /* Direction to rotate the ring */ ! 463: ! 464: if (last_cmd != YANKCMD) ! 465: complain("Yank something first!"); ! 466: ! 467: lfreelist(reg_delete(mp->m_line, mp->m_char, curline, curchar)); ! 468: ! 469: /* Now must find a recently killed region. */ ! 470: ! 471: if (arg_value() < 0) ! 472: dir = 1; ! 473: ! 474: killptr += dir; ! 475: for (;;) { ! 476: if (killptr < 0) ! 477: killptr = NUMKILLS - 1; ! 478: else if (killptr >= NUMKILLS) ! 479: killptr = 0; ! 480: if (killbuf[killptr]) ! 481: break; ! 482: killptr += dir; ! 483: } ! 484: ! 485: this_cmd = YANKCMD; ! 486: ! 487: line = killbuf[killptr]; ! 488: last = lastline(line); ! 489: dot = DoYank(line, 0, last, length(last), curline, curchar, curbuf); ! 490: MarkSet(CurMark(), curline, curchar); ! 491: SetDot(dot); ! 492: } ! 493: ! 494: /* This is an attempt to reduce the amount of memory taken up by each line. ! 495: Without this each malloc of a line uses sizeof (line) + sizeof(HEADER) ! 496: where line is 3 words and HEADER is 1 word. ! 497: This is going to allocate memory in chucks of CHUNKSIZE * sizeof (line) ! 498: and divide each chuck into lineS. A line is free in a chunk when its ! 499: line->l_dline == 0, so freeline sets dline to 0. */ ! 500: ! 501: #define CHUNKSIZE 300 ! 502: ! 503: struct chunk { ! 504: int c_nlines; /* Number of lines in this chunk (so they ! 505: don't all have to be CHUNKSIZE long). */ ! 506: Line *c_block; /* Chunk of memory */ ! 507: struct chunk *c_nextfree; /* Next chunk of lines */ ! 508: }; ! 509: ! 510: private struct chunk *fchunk = 0; ! 511: private Line *ffline = 0; /* First free line */ ! 512: ! 513: void ! 514: freeline(line) ! 515: register Line *line; ! 516: { ! 517: line->l_dline = 0; ! 518: line->l_next = ffline; ! 519: if (ffline) ! 520: ffline->l_prev = line; ! 521: line->l_prev = 0; ! 522: ffline = line; ! 523: } ! 524: ! 525: void ! 526: lfreelist(first) ! 527: register Line *first; ! 528: { ! 529: if (first) ! 530: lfreereg(first, lastline(first)); ! 531: } ! 532: ! 533: /* Append region from line1 to line2 onto the free list of lines */ ! 534: ! 535: void ! 536: lfreereg(line1, line2) ! 537: register Line *line1, ! 538: *line2; ! 539: { ! 540: register Line *next, ! 541: *last = line2->l_next; ! 542: ! 543: while (line1 != last) { ! 544: next = line1->l_next; ! 545: freeline(line1); ! 546: line1 = next; ! 547: } ! 548: } ! 549: ! 550: private int ! 551: newchunk() ! 552: { ! 553: register Line *newline; ! 554: register int i; ! 555: struct chunk *f; ! 556: int nlines = CHUNKSIZE; ! 557: ! 558: f = (struct chunk *) emalloc(sizeof (struct chunk)); ! 559: if (f == 0) ! 560: return 0; ! 561: ! 562: if ((f->c_block = (Line *) malloc((unsigned) (sizeof (Line) * nlines))) == 0) { ! 563: while (nlines > 0) { ! 564: f->c_block = (Line *) malloc((unsigned) (sizeof (Line) * nlines)); ! 565: if (f->c_block != 0) ! 566: break; ! 567: nlines /= 2; ! 568: } ! 569: } ! 570: ! 571: if (nlines <= 0) ! 572: return 0; ! 573: ! 574: f->c_nlines = nlines; ! 575: for (i = 0, newline = f->c_block; i < nlines; newline++, i++) ! 576: freeline(newline); ! 577: f->c_nextfree = fchunk; ! 578: fchunk = f; ! 579: return 1; ! 580: } ! 581: ! 582: /* New BUFfer LINE */ ! 583: ! 584: Line * ! 585: nbufline() ! 586: { ! 587: register Line *newline; ! 588: ! 589: if (ffline == 0) /* No free list */ ! 590: if (newchunk() == 0) ! 591: complain("[Out of lines] "); ! 592: newline = ffline; ! 593: ffline = ffline->l_next; ! 594: if (ffline) ! 595: ffline->l_prev = 0; ! 596: return newline; ! 597: } ! 598: ! 599: /* Remove the free lines, in chunk c, from the free list because they are ! 600: no longer free. */ ! 601: ! 602: private void ! 603: remfreelines(c) ! 604: register struct chunk *c; ! 605: { ! 606: register Line *lp; ! 607: register int i; ! 608: ! 609: for (lp = c->c_block, i = 0; i < c->c_nlines; i++, lp++) { ! 610: if (lp->l_prev) ! 611: lp->l_prev->l_next = lp->l_next; ! 612: else ! 613: ffline = lp->l_next; ! 614: if (lp->l_next) ! 615: lp->l_next->l_prev = lp->l_prev; ! 616: } ! 617: } ! 618: ! 619: /* This is used to garbage collect the chunks of lines when malloc fails ! 620: and we are NOT looking for a new buffer line. This goes through each ! 621: chunk, and if every line in a given chunk is not allocated, the entire ! 622: chunk is `free'd by "free()". */ ! 623: ! 624: void ! 625: GCchunks() ! 626: { ! 627: register struct chunk *cp; ! 628: struct chunk *prev = 0, ! 629: *next = 0; ! 630: register int i; ! 631: register Line *newline; ! 632: ! 633: for (cp = fchunk; cp != 0; cp = next) { ! 634: for (i = 0, newline = cp->c_block; i < cp->c_nlines; newline++, i++) ! 635: if (newline->l_dline != 0) ! 636: break; ! 637: ! 638: next = cp->c_nextfree; ! 639: ! 640: if (i == cp->c_nlines) { /* Unlink it!!! */ ! 641: if (prev) ! 642: prev->c_nextfree = cp->c_nextfree; ! 643: else ! 644: fchunk = cp->c_nextfree; ! 645: remfreelines(cp); ! 646: free((char *) cp->c_block); ! 647: free((char *) cp); ! 648: } else ! 649: prev = cp; ! 650: } ! 651: } ! 652: ! 653: #ifdef LISP ! 654: ! 655: #include "re.h" ! 656: ! 657: /* Grind S-Expr */ ! 658: ! 659: void ! 660: GSexpr() ! 661: { ! 662: Bufpos dot, ! 663: end; ! 664: ! 665: if (linebuf[curchar] != '(') ! 666: complain((char *) 0); ! 667: DOTsave(&dot); ! 668: FSexpr(); ! 669: DOTsave(&end); ! 670: SetDot(&dot); ! 671: for (;;) { ! 672: if (curline == end.p_line) ! 673: break; ! 674: line_move(FORWARD, 1, NO); ! 675: if (!blnkp(linebuf)) ! 676: (void) lisp_indent(); ! 677: } ! 678: SetDot(&dot); ! 679: } ! 680: ! 681: /* lisp_indent() indents a new line in Lisp Mode, according to where ! 682: the matching close-paren would go if we typed that (sort of). */ ! 683: ! 684: private List *specials = NIL; ! 685: ! 686: private void ! 687: init_specials() ! 688: { ! 689: static char *const words[] = { ! 690: "case", ! 691: "def", ! 692: "dolist", ! 693: "fluid-let", ! 694: "lambda", ! 695: "let", ! 696: "lexpr", ! 697: "macro", ! 698: "named-l", /* named-let and named-lambda */ ! 699: "nlambda", ! 700: "prog", ! 701: "selectq", ! 702: 0 ! 703: }; ! 704: char *const *wordp = words; ! 705: ! 706: while (*wordp) ! 707: list_push(&specials, (Element *) *wordp++); ! 708: } ! 709: ! 710: void ! 711: AddSpecial() ! 712: { ! 713: char *word; ! 714: register List *lp; ! 715: ! 716: if (specials == NIL) ! 717: init_specials(); ! 718: word = ask((char *) 0, ProcFmt); ! 719: for (lp = specials; lp != NIL; lp = list_next(lp)) ! 720: if (strcmp((char *) list_data(lp), word) == 0) ! 721: return; /* already in list */ ! 722: (void) list_push(&specials, (Element *) copystr(word)); ! 723: } ! 724: ! 725: Bufpos * ! 726: lisp_indent() ! 727: { ! 728: Bufpos *bp, ! 729: savedot; ! 730: int goal; ! 731: ! 732: bp = m_paren(')', BACKWARD, NO, YES); ! 733: ! 734: if (bp == NULL) ! 735: return NULL; ! 736: ! 737: /* We want to end up ! 738: ! 739: (atom atom atom ... ! 740: ^ here. ! 741: */ ! 742: ! 743: DOTsave(&savedot); ! 744: SetDot(bp); ! 745: f_char(1); ! 746: if (linebuf[curchar] != '(') { ! 747: register List *lp; ! 748: ! 749: if (specials == NIL) ! 750: init_specials(); ! 751: for (lp = specials; lp != NIL; lp = list_next(lp)) ! 752: if (casencmp((char *) list_data(lp), ! 753: &linebuf[curchar], ! 754: strlen((char *) list_data(lp))) == 0) ! 755: break; ! 756: if (lp == NIL) { /* not special */ ! 757: int c_char = curchar; ! 758: ! 759: WITH_TABLE(curbuf->b_major) ! 760: f_word(1); ! 761: END_TABLE(); ! 762: if (LookingAt("[ \t]*;\\|[ \t]*$", linebuf, curchar)) ! 763: curchar = c_char; ! 764: else while (linebuf[curchar] == ' ') ! 765: curchar += 1; ! 766: } else ! 767: curchar += 1; ! 768: } ! 769: goal = calc_pos(linebuf, curchar); ! 770: SetDot(&savedot); ! 771: Bol(); ! 772: n_indent(goal); ! 773: ! 774: return bp; ! 775: } ! 776: #endif /* LISP */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.