1 21 22 package org.armedbear.j; 23 24 import gnu.regexp.RE; 25 import gnu.regexp.REMatch; 26 import gnu.regexp.UncheckedRE; 27 28 public final class LispFormatter extends Formatter 29 { 30 private static final int STATE_OPEN_PAREN = STATE_LAST + 1; 32 private static final int STATE_CLOSE_PAREN = STATE_LAST + 2; 33 private static final int STATE_CAR = STATE_LAST + 3; 34 private static final int STATE_DEFUN = STATE_LAST + 4; 35 private static final int STATE_DEFINITION = STATE_LAST + 5; 36 private static final int STATE_NAME = STATE_LAST + 6; 37 private static final int STATE_SUBSTITUTION = STATE_LAST + 7; 38 private static final int STATE_SECONDARY_KEYWORD = STATE_LAST + 8; 39 private static final int STATE_PUNCTUATION = STATE_LAST + 9; 40 private static final int STATE_ARGLIST = STATE_LAST + 10; 41 private static final int STATE_QUOTED_LIST = STATE_LAST + 11; 42 43 private static final int LISP_FORMAT_TEXT = 0; 45 private static final int LISP_FORMAT_COMMENT = 1; 46 private static final int LISP_FORMAT_STRING = 2; 47 private static final int LISP_FORMAT_KEYWORD = 3; 48 private static final int LISP_FORMAT_DEFUN = 4; 49 private static final int LISP_FORMAT_NAME = 5; 50 private static final int LISP_FORMAT_PARENTHESIS = 6; 51 private static final int LISP_FORMAT_PUNCTUATION = 7; 52 private static final int LISP_FORMAT_SUBSTITUTION = 8; 53 private static final int LISP_FORMAT_SECONDARY_KEYWORD = 9; 54 55 private static final RE condRE = 56 new UncheckedRE("\\([ \t]*cond[ \t]*\\(\\("); 57 58 private static final RE dolistRE = 59 new UncheckedRE("\\([ \t]*dolist[ \t]*\\("); 60 61 private static final RE doRE = 63 new UncheckedRE("\\([ \t]*do\\*?[ \t]*\\(.*\\)[ \t]\\(\\("); 64 65 private static final RE letOrDoRE = 66 new UncheckedRE("\\([ \t]*(let|do)\\*?[ \t]*\\(\\("); 67 68 private final Mode mode; 69 70 public LispFormatter(Buffer buffer) 71 { 72 this.buffer = buffer; 73 this.mode = buffer.getMode(); 74 } 75 76 private Line currentLine; 77 private int tokenBegin = 0; 78 79 private void endToken(String text, int tokenEnd, int state) 80 { 81 if (tokenEnd - tokenBegin > 0) { 82 int format = LISP_FORMAT_TEXT; 83 switch (state) { 84 case STATE_NEUTRAL: 85 case STATE_ARGLIST: 86 case STATE_QUOTED_LIST: 87 break; 88 case STATE_QUOTE: 89 format = LISP_FORMAT_STRING; 90 break; 91 case STATE_OPEN_PAREN: 92 case STATE_CLOSE_PAREN: 93 format = LISP_FORMAT_PARENTHESIS; 94 break; 95 case STATE_CAR: 96 break; 97 case STATE_DEFUN: { 98 String token = text.substring(tokenBegin, tokenEnd).trim(); 99 if (isKeyword(token)) { 100 if (isDefiner(token)) 101 format = LISP_FORMAT_DEFUN; 102 else 103 format = LISP_FORMAT_KEYWORD; 104 } 105 break; 106 } 107 case STATE_NAME: 108 format = LISP_FORMAT_NAME; 109 break; 110 case STATE_DEFINITION: 111 case STATE_IDENTIFIER: 112 break; 113 case STATE_SECONDARY_KEYWORD: 114 format = LISP_FORMAT_SECONDARY_KEYWORD; 115 break; 116 case STATE_SUBSTITUTION: 117 format = LISP_FORMAT_SUBSTITUTION; 118 break; 119 case STATE_COMMENT: 120 format = LISP_FORMAT_COMMENT; 121 break; 122 case STATE_PUNCTUATION: 123 format = LISP_FORMAT_PUNCTUATION; 124 } 125 addSegment(text, tokenBegin, tokenEnd, format); 126 tokenBegin = tokenEnd; 127 } 128 } 129 130 private static final boolean isDefiner(String s) 131 { 132 if (s.length() >= 5 && s.startsWith("def")) { 133 String translated = LispMode.translateDefiner(s); 134 if (translated != null) { 135 if (translated.equals("defconstant")) 137 return false; 138 if (translated.equals("defparameter")) 139 return false; 140 if (translated.equals("defvar")) 141 return false; 142 return true; 143 } 144 } 145 return false; 146 } 147 148 private static final boolean isPositionFunctional( 151 final String text, final int offset, final Line line) { 155 if (offset >= 1 && text.charAt(offset-1) == '(') { 156 if (offset >= 2 && text.charAt(offset-2) == '(') { 157 if (countLeadingSpaces(text) == offset-2) { 159 Position pos = 161 LispMode.findContainingSexp(new Position(line, 0)); 162 if (pos != null) { 163 pos.skip(); 165 String s = parseToken(pos).toLowerCase(); 166 if (s.equals("cond")) 167 return true; 168 if (s.equals("do") || s.equals("do*")) 170 return true; 171 } 172 } 173 REMatch m = condRE.getMatch(text); 174 if (m != null && m.getEndIndex() == offset) { 175 return true; 176 } 177 m = doRE.getMatch(text); 178 if (m != null && m.getEndIndex() == offset) 179 return true; 180 return false; 181 } 182 if (countLeadingSpaces(text) == offset-1) { 184 Position pos = 186 LispMode.findContainingSexp(new Position(line, 0)); 187 if (pos != null) { 188 if (pos.lookingAt("((")) { 189 REMatch m = 190 letOrDoRE.getMatch(pos.getLine().getText()); 191 if (m != null && m.getEndIndex() == pos.getOffset() + 2) 192 return false; 193 } else { 194 pos.skip(); 196 String s = parseToken(pos).toLowerCase(); 197 if (s.equals("case")) 198 return false; 199 if (s.equals("ccase")) 200 return false; 201 if (s.equals("ecase")) 202 return false; 203 if (s.equals("typecase")) 204 return false; 205 if (s.equals("ctypecase")) 206 return false; 207 if (s.equals("etypecase")) 208 return false; 209 } 210 } 211 } else { 212 REMatch m = dolistRE.getMatch(text); 214 if (m != null && m.getEndIndex() == offset) 215 return false; 216 } 217 } 218 return true; 219 } 220 221 private static final String parseToken(Position pos) 224 { 225 final Line line = pos.getLine(); 226 final int limit = line.length(); 227 int begin = pos.getOffset(); 228 while (begin < limit && Character.isWhitespace(line.charAt(begin))) 229 ++begin; 230 if (begin == limit) 231 return ""; 232 int end = begin + 1; 233 while (end < limit && !Character.isWhitespace(line.charAt(end))) 234 ++end; 235 return line.getText().substring(begin, end); 236 } 237 238 private static final int countLeadingSpaces(String s) 239 { 240 final int limit = s.length(); 241 for (int i = 0; i < limit; i++) { 242 if (s.charAt(i) != ' ') 243 return i; 244 } 245 return limit; 246 } 247 248 private void parseLine(Line line) 249 { 250 currentLine = line; 251 tokenBegin = 0; 252 final String text = getDetabbedText(line); 253 int state = line.flags(); 254 clearSegmentList(); 255 final int limit = text.length(); 256 int i = 0; 257 while (i < limit) { 258 char c = text.charAt(i); 259 if (c == '\\' && i < limit-1) { 260 i += 2; 261 continue; 262 } 263 if (state == STATE_COMMENT) { 264 if (c == '|' && i < limit-1) { 265 c = text.charAt(i+1); 266 if (c == '#') { 267 i += 2; 268 endToken(text, i, state); 269 state = STATE_NEUTRAL; 270 continue; 271 } 272 } 273 ++i; 274 continue; 275 } 276 if (state == STATE_QUOTE) { 277 if (c == '"') { 278 endToken(text, i+1, state); 279 state = STATE_NEUTRAL; 280 } 281 ++i; 282 continue; 283 } 284 if (c == '"') { 286 endToken(text, i, state); 287 state = STATE_QUOTE; 288 ++i; 289 continue; 290 } 291 if (c == ';') { 292 endToken(text, i, state); 293 endToken(text, limit, STATE_COMMENT); 294 return; 295 } 296 if (c == '#' && i < limit - 1) { 297 endToken(text, i, state); 298 c = text.charAt(i + 1); 299 if (c == '|') { 300 state = STATE_COMMENT; 301 i += 2; 302 continue; 303 } 304 if (c == '\'') { 305 i += 2; 306 continue; 307 } 308 if (c == ':') { 309 i += 2; 311 continue; 312 } 313 state = STATE_NEUTRAL; 314 ++i; 315 continue; 316 } 317 if (c == '\'') { 318 endToken(text, i, state); 319 state = STATE_NEUTRAL; 320 i = skipQuotedObject(text, ++i, state); 321 continue; 322 } 323 if (c == '`') { 324 endToken(text, i, state); 326 state = STATE_PUNCTUATION; 327 ++i; 328 endToken(text, i, state); 329 state = STATE_NEUTRAL; 330 continue; 331 } 332 if (c == ',') { 333 endToken(text, i, state); 334 state = STATE_PUNCTUATION; 335 ++i; 336 if (i < limit) { 337 c = text.charAt(i); 338 if (c == '@' || c == '.') 339 ++i; 340 } 341 endToken(text, i, state); 342 state = STATE_SUBSTITUTION; 343 continue; 344 } 345 if (state == STATE_ARGLIST) { 346 if (c == '(') { 347 endToken(text, i, state); 348 ++i; 349 endToken(text, i, STATE_OPEN_PAREN); 350 continue; 351 } 352 } 353 if (c == '(') { 354 endToken(text, i, state); 355 state = STATE_OPEN_PAREN; 356 ++i; 357 continue; 358 } 359 if (c == ')') { 360 endToken(text, i, state); 361 state = STATE_CLOSE_PAREN; 362 ++i; 363 continue; 364 } 365 if (state == STATE_OPEN_PAREN) { 366 if (c == ':' || c == '&') { 367 endToken(text, i, state); 368 state = STATE_SECONDARY_KEYWORD; 369 } else if (!Character.isWhitespace(c)) { 370 endToken(text, i, state); 371 if (isPositionFunctional(text, i, currentLine)) 372 state = STATE_DEFUN; 373 else 374 state = STATE_CAR; 375 } 376 ++i; 377 continue; 378 } 379 if (state == STATE_CLOSE_PAREN) { 380 if (c != ')') { 381 endToken(text, i, state); 382 state = STATE_NEUTRAL; 383 } 384 ++i; 385 continue; 386 } 387 if (state == STATE_CAR) { 388 if (Character.isWhitespace(c)) { 389 endToken(text, i, state); 390 state = STATE_NEUTRAL; 391 } 392 ++i; 393 continue; 394 } 395 if (state == STATE_DEFUN) { 396 if (Character.isWhitespace(c)) { 397 endToken(text, i, state); 398 LineSegment s = segmentList.getLastSegment(); 399 if (s != null) { 400 String translated = LispMode.translateDefiner(s.getText()); 401 if (translated != null && isDefiner(translated)) { 402 state = STATE_DEFINITION; 403 ++i; 404 continue; 405 } 406 } 407 state = STATE_NEUTRAL; 408 } 409 ++i; 410 continue; 411 } 412 if (state == STATE_NAME) { 413 if (!mode.isIdentifierPart(c) && c != ':') { 414 endToken(text, i, state); 415 state = STATE_ARGLIST; 416 } 417 ++i; 418 continue; 419 } 420 if (state == STATE_IDENTIFIER) { 421 if (!mode.isIdentifierPart(c) && c != ':') { 422 endToken(text, i, state); 423 state = STATE_NEUTRAL; 424 } 425 ++i; 426 continue; 427 } 428 if (state == STATE_SECONDARY_KEYWORD || 429 state == STATE_SUBSTITUTION) { 430 if (!mode.isIdentifierPart(c)) { 431 endToken(text, i, state); 432 state = STATE_NEUTRAL; 433 } 434 ++i; 435 continue; 436 } 437 if (state == STATE_DEFINITION) { 438 if (mode.isIdentifierStart(c)) 439 state = STATE_NAME; 440 ++i; 441 continue; 442 } 443 if (state == STATE_NEUTRAL || state == STATE_ARGLIST || 444 state == STATE_QUOTED_LIST) { 445 if (c == ':' || c == '&') { 446 endToken(text, i, state); 447 state = STATE_SECONDARY_KEYWORD; 448 } else if (mode.isIdentifierStart(c)) { 449 endToken(text, i, state); 450 state = STATE_IDENTIFIER; 451 } else ; 453 } 454 ++i; 455 } 456 endToken(text, i, state); 457 } 458 459 public LineSegmentList formatLine(Line line) 460 { 461 if (line == null) { 462 clearSegmentList(); 463 addSegment("", LISP_FORMAT_TEXT); 464 return segmentList; 465 } 466 parseLine(line); 467 return segmentList; 468 } 469 470 public boolean parseBuffer() 471 { 472 int state = STATE_NEUTRAL; 473 boolean changed = false; 474 Position pos = new Position(buffer.getFirstLine(), 0); 475 while (!pos.atEnd()) { 476 char c = pos.getChar(); 477 if (c == EOL) { 478 if (pos.nextLine()) { 479 changed = 480 setLineFlags(pos.getLine(), state) || changed; 481 continue; 482 } else 483 break; } 485 if (c == '\\') { 486 pos.skip(); 488 pos.next(); 489 continue; 490 } 491 if (c == ';') { 493 if (pos.nextLine()) { 495 changed = 496 setLineFlags(pos.getLine(), state) || changed; 497 continue; 498 } else { 499 pos.moveTo(pos.getLine(), pos.getLine().length()); 500 break; } 502 } 503 if (c == '#') { 504 if (pos.lookingAt("#|")) { 505 pos.skip(2); 506 changed = skipBalancedComment(pos) || changed; 507 } else if (pos.lookingAt("#'")) 508 pos.skip(2); 509 else 510 pos.skip(); 511 continue; 512 } 513 if (c == '"') { 514 pos.skip(); 515 changed = skipString(pos) || changed; 516 continue; 517 } 518 if (c == '\'') { 519 pos.skip(); 520 changed = skipQuotedObject(pos) || changed; 521 continue; 522 } 523 if (c == '(') { 524 state = STATE_OPEN_PAREN; 525 pos.skip(); 526 continue; 527 } 528 if (state == STATE_OPEN_PAREN) { 529 if (!Character.isWhitespace(c)) 530 state = STATE_CAR; 531 pos.next(); 532 continue; 533 } 534 if (state == STATE_CAR) { 535 if (c == ')' || Character.isWhitespace(c)) 536 state = STATE_NEUTRAL; 537 pos.next(); 538 continue; 539 } 540 pos.skip(); 542 continue; 543 } 544 buffer.setNeedsParsing(false); 545 return changed; 546 } 547 548 private static boolean skipString(Position pos) 549 { 550 boolean changed = false; 551 while (!pos.atEnd()) { 552 char c = pos.getChar(); 553 if (c == EOL) { 554 if (pos.nextLine()) { 555 changed = 556 setLineFlags(pos.getLine(), STATE_QUOTE) || changed; 557 continue; 558 } else 559 break; } 561 if (c == '\\') { 562 pos.skip(); 564 if (pos.getChar() == EOL) { 565 if (pos.nextLine()) { 566 changed = 567 setLineFlags(pos.getLine(), STATE_QUOTE) || changed; 568 continue; 569 } else 570 break; } else { 572 pos.next(); 574 continue; 575 } 576 } 577 if (c == '"') { 578 pos.next(); 579 break; 580 } 581 pos.skip(); 583 } 584 return changed; 585 } 586 587 private static boolean skipBalancedComment(Position pos) 588 { 589 boolean changed = false; 590 int count = 1; 591 while (!pos.atEnd()) { 592 char c = pos.getChar(); 593 if (c == EOL) { 594 if (pos.nextLine()) { 595 changed = 596 setLineFlags(pos.getLine(), STATE_COMMENT) || changed; 597 continue; 598 } else 599 break; } 601 if (c == '\\') { 602 pos.skip(); 604 pos.next(); 605 continue; 606 } 607 if (c == '#' && pos.lookingAt("#|")) { 608 pos.skip(2); 609 ++count; 610 continue; 611 } 612 if (c == '|' && pos.lookingAt("|#")) { 613 pos.skip(2); 614 if (--count == 0) 615 break; else 617 continue; 618 } 619 pos.skip(); 621 } 622 return changed; 623 } 624 625 private int skipQuotedObject(String text, int i, int state) 626 { 627 int count = 0; 628 final int limit = text.length(); 629 while (i < limit && Character.isWhitespace(text.charAt(i))) 631 ++i; 632 while (i < limit) { 633 switch (text.charAt(i)) { 634 case ' ': 635 case '\t': 636 return i; 637 case '(': 638 endToken(text, i, state); 639 ++count; 640 ++i; 641 endToken(text, i, STATE_OPEN_PAREN); 642 break; 643 case ')': 644 endToken(text, i, state); 645 ++i; 646 endToken(text, i, STATE_CLOSE_PAREN); 647 if (--count <= 0) 648 return i; 649 break; 650 case '\\': 651 ++i; 652 if (i < limit) 653 ++i; 654 break; 655 case ';': 656 case ',': 657 case '"': 658 return i; 659 case ':': 660 if (i > 0) { 661 char c = text.charAt(i - 1); 662 if (!mode.isIdentifierPart(c) && c != ':') 663 return i; 664 } 665 ++i; 666 break; 667 default: 668 ++i; 669 break; 670 } 671 } 672 return i; 673 } 674 675 private static boolean skipQuotedObject(Position pos) 676 { 677 boolean changed = false; 678 int count = 0; 679 while (!pos.atEnd()) { 680 char c = pos.getChar(); 681 if (c == EOL) { 682 if (pos.nextLine()) { 683 changed = 684 setLineFlags(pos.getLine(), STATE_QUOTED_LIST) || changed; 685 continue; 686 } else 687 break; } 689 if (Character.isWhitespace(c)) { 690 pos.skip(); 691 continue; 692 } 693 if (c == '\\') { 694 pos.skip(); 695 pos.next(); 696 continue; 697 } 698 if (c == '"') { 699 pos.skip(); 700 changed = skipString(pos) || changed; 701 continue; 702 } 703 if (c == '#' && pos.lookingAt("#(")) { 704 ++count; 705 pos.skip(2); 706 continue; 707 } 708 if (c == '(') { 709 ++count; 710 pos.skip(); 711 continue; 712 } 713 if (c == ')') { 714 pos.skip(); 715 if (count > 0) { 716 --count; 717 if (count == 0) 718 break; 719 } 720 continue; 721 } 722 if (count == 0) { 724 skipToken(pos); 725 break; 726 } 727 pos.skip(); 729 } 730 return changed; 731 } 732 733 private static boolean setLineFlags(Line line, int newFlags) 734 { 735 if (line.flags() == newFlags) 736 return false; line.setFlags(newFlags); 738 return true; 739 } 740 741 private static void skipToken(Position pos) 742 { 743 while (!Character.isWhitespace(pos.getChar()) && pos.next()) 744 ; 745 } 746 747 public FormatTable getFormatTable() 748 { 749 if (formatTable == null) { 750 formatTable = new FormatTable("LispMode"); 751 formatTable.addEntryFromPrefs(LISP_FORMAT_TEXT, "text"); 752 formatTable.addEntryFromPrefs(LISP_FORMAT_COMMENT, "comment"); 753 formatTable.addEntryFromPrefs(LISP_FORMAT_STRING, "string"); 754 formatTable.addEntryFromPrefs(LISP_FORMAT_KEYWORD, "keyword"); 755 formatTable.addEntryFromPrefs(LISP_FORMAT_DEFUN, "keyword"); 756 formatTable.addEntryFromPrefs(LISP_FORMAT_NAME, "function"); 757 formatTable.addEntryFromPrefs(LISP_FORMAT_PARENTHESIS, 758 "parenthesis","text"); 759 formatTable.addEntryFromPrefs(LISP_FORMAT_PUNCTUATION, 760 "punctuation", "text"); 761 formatTable.addEntryFromPrefs(LISP_FORMAT_SUBSTITUTION, 762 "substitution", "text"); 763 formatTable.addEntryFromPrefs(LISP_FORMAT_SECONDARY_KEYWORD, 764 "secondaryKeyword", "text"); 765 } 766 return formatTable; 767 } 768 } 769 | Popular Tags |