KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > armedbear > j > LispFormatter


1 /*
2  * LispFormatter.java
3  *
4  * Copyright (C) 1998-2004 Peter Graves
5  * $Id: LispFormatter.java,v 1.35 2004/04/11 19:10:40 piso Exp $
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */

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     // States.
31
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     // Formats.
44
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     // Matches e.g. "(do () ((endp list1))".
62
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 JavaDoc 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 JavaDoc 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 JavaDoc s)
131     {
132         if (s.length() >= 5 && s.startsWith("def")) {
133             String JavaDoc translated = LispMode.translateDefiner(s);
134             if (translated != null) {
135                 // Exclude DEFCONSTANT, DEFPARAMETER, DEFVAR.
136
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     // Returns true if token at specified offset in detabbed text from line is
149
// in functional position, based on context.
150
private static final boolean isPositionFunctional(
151         final String JavaDoc text, // Detabbed text.
152
final int offset, // Offset of token in detabbed text.
153
final Line line) // Line (which may contain tab characters).
154
{
155         if (offset >= 1 && text.charAt(offset-1) == '(') {
156             if (offset >= 2 && text.charAt(offset-2) == '(') {
157                 // Token is preceded by "((".
158
if (countLeadingSpaces(text) == offset-2) {
159                     // First non-whitespace text on line.
160
Position pos =
161                         LispMode.findContainingSexp(new Position(line, 0));
162                     if (pos != null) {
163                         // Skip '('.
164
pos.skip();
165                         String JavaDoc s = parseToken(pos).toLowerCase();
166                         if (s.equals("cond"))
167                             return true;
168                         // Check for end-test form after DO/DO*.
169
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             // Text is preceded by single '('.
183
if (countLeadingSpaces(text) == offset-1) {
184                 // First non-whitespace on line.
185
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                         // Skip '('.
195
pos.skip();
196                         String JavaDoc 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                 // Not first whitespace on line.
213
REMatch m = dolistRE.getMatch(text);
214                 if (m != null && m.getEndIndex() == offset)
215                     return false;
216             }
217         }
218         return true;
219     }
220
221     // Returns next whitespace-delimited token starting at (or after) pos.
222
// Same line only. Never returns null.
223
private static final String JavaDoc 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 JavaDoc 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 JavaDoc 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             // Reaching here, we're not in a comment or quoted string.
285
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                     // Uninterned symbol.
310
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                 // Backquote.
325
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 JavaDoc 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 // Still neutral...
452
;
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; // Reached end of buffer.
484
}
485             if (c == '\\') {
486                 // Escape.
487
pos.skip();
488                 pos.next();
489                 continue;
490             }
491             // Not in comment or quoted string.
492
if (c == ';') {
493                 // Single-line comment beginning. Ignore rest of line.
494
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; // Reached end of buffer.
501
}
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             // Default.
541
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; // Reached end of buffer.
560
}
561             if (c == '\\') {
562                 // Escape.
563
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; // End of buffer.
571
} else {
572                     // Not end of line.
573
pos.next();
574                     continue;
575                 }
576             }
577             if (c == '"') {
578                 pos.next();
579                 break;
580             }
581             // Default.
582
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; // End of buffer.
600
}
601             if (c == '\\') {
602                 // Escape.
603
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; // End of comment.
616
else
617                     continue;
618             }
619             // Default.
620
pos.skip();
621         }
622         return changed;
623     }
624
625     private int skipQuotedObject(String JavaDoc text, int i, int state)
626     {
627         int count = 0;
628         final int limit = text.length();
629         // Skip whitespace after quote character.
630
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; // End of buffer.
688
}
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             // Not EOL, whitespace or paren.
723
if (count == 0) {
724                 skipToken(pos);
725                 break;
726             }
727             // Default.
728
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; // No change.
737
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