1 package acl2s.plugin.parse;
2
3 import java.util.ArrayList;
4
5 import org.eclipse.jface.text.BadLocationException;
6 import org.eclipse.jface.text.DocumentEvent;
7 import org.eclipse.jface.text.IDocument;
8 import org.eclipse.jface.text.IDocumentListener;
9 import org.eclipse.jface.text.IRegion;
10 import org.eclipse.jface.text.Region;
11 import org.eclipse.jface.text.source.IAnnotationModel;
12 import org.peterd.util.Misc;
13
14 import acl2s.lib.contentassist.*;
15 import acl2s.lib.parse.obj.Sym;
16 import acl2s.plugin.Acl2sPlugin;
17 import acl2s.plugin.editors.A2sDocument;
18
19 public class LispTokens implements IDocumentListener {
20 protected class PackageRange {
21 public int start, end, index;
22 public PackageRange (int start, int end, int index) { this.start = start; this.end = end; this.index = index; }
23 }
24 protected LispToken[] tokens = null;
25 protected int size = 0;
26 protected int sizePow2 = 0; // just a view; largest pow2 not greater than size
27 protected IDocument doc = null;
28 protected IAnnotationModel model = null;
29 protected ArrayList<PackageRange> packageRanges;
30
31 protected transient final ArrayList<LispToken> tokenq = new ArrayList<LispToken>(100);
32 protected transient IRegion damage;
33 protected transient DocumentParser dp = null;
34
35 public void connect(IDocument doc) {
36 if (this.doc != null) throw new IllegalStateException();
37 this.doc = doc;
38
39 dp = new DocumentParser(null,doc);
40 tokens = new LispToken[100];
41 packageRanges = new ArrayList<PackageRange>(3);
42 reparse();
43 doc.addPrenotifiedDocumentListener(this);
44 }
45
46 public void disconnect() {
47 doc.removeDocumentListener(this);
48 doc = null;
49 tokens = null;
50 size = 0;
51 dp = null;
52 model = null;
53 }
54
55 public void reparse() {
56 for (int i = 0; i < tokens.length; i++) {
57 if (tokens[i] != null) {
58 tokens[i].removeAllAnnotations(model);
59 tokens[i] = null;
60 }
61 }
62 tokens[0] = new LispToken();
63 tokens[0].type = LispToken.EOF_TOK;
64 size = 1;
65 sizePow2 = 1;
66 documentChanged(new DocumentEvent(doc,0,0,doc.get()));
67 }
68
69 private int packageIndex(int offset) {
70 for (PackageRange p : packageRanges) {
71 if (p.start <= offset && (p.end < 0 || offset < p.end)) // p.end == -1 means no end has been prescribed
72 return p.index;
73 }
74 return -1;
75 }
76
77 private void setSizePow2() {
78 int v = 1;
79 while (v << 1 <= size) v <<= 1;
80 sizePow2 = v;
81 }
82
83 public int getTokenCount() { return size; }
84
85 public LispToken getToken(int idx) {
86 if (idx < 0 || idx >= size) throw new IndexOutOfBoundsException();
87 return tokens[idx];
88 }
89
90 public int getTokenIdxContaining(int docOffset) {
91 if (size == 0) throw new IllegalStateException();
92 int docLen = tokens[size-1].absoluteEnd();
93 if (docLen == 0) {
94 return 0;
95 }
96
97 int i = 0;
98 for (int delta = sizePow2; delta > 0; delta >>= 1) { // binary search
99 int newi = i + delta;
100 if (newi < size && tokens[newi].absoluteStart() <= docOffset) {
101 i = newi;
102 }
103 }
104 return i;
105 }
106
107 public LispToken getTokenAtOffset(int docOffset)
108 {
109 int ind = getTokenIdxContaining(docOffset);
110 return (tokens[ind].absoluteEnd() >= docOffset) ? tokens[ind] : null;
111 }
112
113 public int matchParenTok(int idx) {
114 if (idx < 0 || idx >= size) throw new IllegalArgumentException();
115 int delta = tokens[idx].parenDelta();
116 if (delta == 0) throw new IllegalArgumentException();
117 int balance = delta;
118 int i;
119 for (i = idx + delta; i >= 0 && i < size; i += delta) {
120 balance += tokens[i].parenDelta();
121 if (balance == 0) return i;
122 }
123 return i;
124 }
125
126 public LispToken matchCommaTok(LispToken comma) {
127 if (comma.type != LispToken.COMMA_TOK &&
128 comma.type != LispToken.COMAT_TOK) return null;
129 return LispToken.matchComma(comma.ctx,1);
130 }
131
132 public boolean hasErrors() {
133 for (int i = 0; i < size; i++) {
134 if (tokens[i].hasErrors()) return true;
135 }
136 return false;
137 }
138
139 public void documentAboutToBeChanged(DocumentEvent event) { }
140
141 protected LispToken parseToken(int pos, IAnnotationModel model) {
142 dp.setPos(pos);
143 return LispToken.parse(dp,model);
144 }
145
146 protected void invalidate(int tokIdx) {
147 LispToken target = tokens[tokIdx];
148 for (TokenInvalidationListener l : invalidationListeners) {
149 l.tokenAboutToBeInvalidated(doc, this, target);
150 }
151 target.offset = -1;
152 target.removeAllAnnotations(model);
153 }
154
155 private String []getSignature(int fromInd) throws BadLocationException {
156 ArrayList<String> buildSig = new ArrayList<String>();
157 int i = fromInd;
158 // skip opening paren (error if more than one, but who am I to judge?
159 while (i < tokens.length && tokens[i] != null && tokens[i].type == LispToken.OPAREN_TOK) ++i;
160 if (i != fromInd && i < tokens.length) { // == means no open paren. Must be nil.
161 // add all following symbols. Stops at closing paren
162 while (i < tokens.length && tokens[i] != null && tokens[i].type == LispToken.SYM_TOK) {
163 buildSig.add(doc.get(tokens[i].offset, tokens[i].length));
164 ++i;
165 }
166 }
167 return buildSig.toArray(new String[buildSig.size()]);
168 }
169
170 protected static final LispToken preToken = new LispToken();
171 protected static final boolean tokenize = true;
172
173 public void documentChanged(DocumentEvent event) {
174 if (doc instanceof A2sDocument) {
175 model = ((A2sDocument) doc).getAnnotationModel();
176 }
177
178 int docLen = doc.getLength();
179 //long stime = System.currentTimeMillis();
180 if (!tokenize || model == null) {
181 dp.setPos(docLen);
182 tokens[0] = LispToken.parse(dp,model);
183 size = 1;
184 damage = new Region(event.fOffset,event.fLength);
185 return;
186 }
187
188 final int eventReplStart = event.fOffset;
189 final int eventReplEnd = event.fOffset + event.fLength;
190
191 // find first modified token
192 int i;
193 if (eventReplStart > 0) {
194 i = getTokenIdxContaining(eventReplStart - 1);
195 } else {
196 i = getTokenIdxContaining(eventReplStart); // i = 0; ?
197 }
198
199 // TODO change this for nested encapsulates
200 int topLevelTokenIdx = i;
201 while (topLevelTokenIdx > 0 && tokens[topLevelTokenIdx].ctx != null) --topLevelTokenIdx;
202
203 // update indexed symbols
204 WorkspaceDictionary.removeSymbolsInRange(doc, tokens[topLevelTokenIdx].offset, eventReplEnd-1);
205
206 final int firstIdxInRegion = i;
207
208 int damageStart = tokens[firstIdxInRegion].absoluteStart();
209
210 // invalidate tokens within replaced area
211 // => (old offset == new offset) -> same parse
212 invalidate(i);
213 i++;
214 for (/*i = first*/; i < size && tokens[i].absoluteStart() < eventReplEnd; i++) {
215 invalidate(i);
216 }
217
218 final int firstIdxPastRegion = i;
219
220 if (Acl2sPlugin.DEBUG && firstIdxPastRegion == firstIdxInRegion) {
221 //System.out.println("Interesting...");
222 throw new IllegalStateException();
223 }
224
225 // shift those after
226 final int shift = event.fText.length() - event.fLength;
227 if (shift != 0) {
228 for (; i < size; i++) {
229 tokens[i].offset += shift;
230 }
231 }
232
233 // do magic
234 //tokenq.clear(); // already done
235 LispToken prev = (firstIdxInRegion == 0) ? preToken : tokens[firstIdxInRegion-1];
236 i = firstIdxPastRegion;
237 for (;;) {
238 int pos = prev.absoluteEnd();
239
240 LispToken t = parseToken(pos,model);
241
242 t.setCtx(prev.getPostCtx(), model);
243
244 while (i < size && tokens[i].offset < t.offset) {
245 invalidate(i);
246 i++;
247 }
248 if (i < size && tokens[i].offset == t.offset) {
249 tokens[i].removeAllAnnotations(model);
250 tokens[i] = t;
251 break;
252 }
253 tokenq.add(t);
254 if (t.type == LispToken.EOF_TOK) {
255 break;
256 }
257 prev = t;
258 }
259 final int meetIdx = i;
260
261 /* Annotation debugging:
262 System.err.println("Annotations:");
263 Iterator foo = model.getAnnotationIterator();
264 while (foo.hasNext()) {
265 System.err.println(foo.next().toString());
266 }//*/
267
268 // damage area
269 int damageEnd = i < size ? tokens[i].absoluteEnd() : docLen;
270 damage = new Region(damageStart,damageEnd-damageStart);
271
272 final int oldLen = meetIdx - firstIdxInRegion;
273 final int newLen = tokenq.size();
274
275 // compute number of indices to shift meetIdx...size-1
276 final int idxShift = newLen - oldLen;
277 if (idxShift > 0) {
278 // grow token array if necessary
279 if (size + idxShift > tokens.length) {
280 LispToken[] newTokens = new LispToken[(size + idxShift) * 2];
281 System.arraycopy(tokens,0,newTokens,0,size);
282 tokens = newTokens;
283 }
284
285 // do shift
286 for (i = size - 1; i >= meetIdx; i--) {
287 tokens[i + idxShift] = tokens[i];
288 }
289 size += idxShift;
290 i += idxShift;
291
292 // now, after shift, meetIdx-firstIdxInRegion == tokenq.size()
293 // fill that area from tokenq
294 for (;i >= firstIdxInRegion; i--) {
295 tokens[i] = tokenq.get(i-firstIdxInRegion);
296 }
297 } else {
298 // fill from tokenq
299 final int max = firstIdxInRegion + newLen;
300 for (i = firstIdxInRegion; i < max; i++) {
301 tokens[i] = tokenq.get(i-firstIdxInRegion);
302 }
303
304 if (idxShift < 0) {
305 // do shift
306 size += idxShift; // actually DECREASES size
307 for (; i < size; i++) {
308 tokens[i] = tokens[i - idxShift];
309 }
310
311 // optional:
312 for (; i < size - idxShift; i++) {
313 tokens[i] = null;
314 }
315 } else {
316 //no shift!
317 }
318 }
319 // TODO: go through tokens from firstIdxInRegion to firstIdxInRegion+newLen and index symbols
320 // want first top level token from damaged area
321 i = topLevelTokenIdx;
322 int currentPackageIndex = packageIndex(tokens[i].absoluteStart());
323 int endIdx = firstIdxInRegion + newLen;
324 while (i < endIdx) {
325 // look for top level tokens. Encapsulate special case.
326 if (tokens[i] != null && tokens[i].ctx == null) {
327 // skip open parens to get to first symbol
328 while (i < endIdx && tokens[i] != null && tokens[i].type == LispToken.OPAREN_TOK) ++i;
329 if (i >= endIdx || tokens[i] == null || tokens[i].type != LispToken.SYM_TOK) { ++i; continue; }
330
331 try {
|
Event do_not_call: |
"java.lang.String.toLowerCase()" implicitly uses the environment's default character set, which might lead to unexpected behavior. Consider using toLowerCase(Locale locale). |
332 String symbol = doc.get(tokens[i].absoluteStart(), tokens[i].length).toLowerCase();
333 do {
334 ++i;
335 } while (i < endIdx && tokens[i] != null && tokens[i].type == LispToken.SPACE_TOK);
336 if (i >= endIdx || tokens[i] == null) { ++i; continue; }
337 String name = doc.get(tokens[i].offset, tokens[i].length);
338 if (name.trim().length() == 0) { ++i; continue; }
339 String []signature = null;
340 if (symbol.equals("defun")) {
341 signature = getSignature(i+1);
342 WorkspaceDictionary.addSymbol(doc, name, FileDictionary.CharacterTreeNodeType.DEFUN, currentPackageIndex, tokens[i].offset, signature);
343 } else if (symbol.equals("defmacro")) {
344 signature = getSignature(i+1);
345 WorkspaceDictionary.addSymbol(doc, name, FileDictionary.CharacterTreeNodeType.DEFMACRO, currentPackageIndex, tokens[i].offset, signature);
346 } else if (symbol.equals("defthm")) {
347 WorkspaceDictionary.addSymbol(doc, name, FileDictionary.CharacterTreeNodeType.DEFTHM, currentPackageIndex, tokens[i].offset, signature);
348 } else if (symbol.equals("defconst")) {
349 WorkspaceDictionary.addSymbol(doc, name, FileDictionary.CharacterTreeNodeType.DEFCONST, currentPackageIndex, tokens[i].offset, signature);
350 } else if (symbol.equals("local")) {
351 // TODO handle mid level common names
352 } else if (symbol.equals("encapsulate")) {
353 // TODO handle encapsulate with locals and nested encapsulates
354 } else if (symbol.equals("in-package")) {
355 // TODO create a new package range and close the previous package range
356 }
357 } catch (BadLocationException ble) {
358 break;
359 }
360 }
361 ++i;
362 }
363 // update contexts
364 //*
365 i = firstIdxInRegion + newLen + 1;
366 for (;;) {
367 if (i >= size) break;
368 LispToken newCtx = tokens[i-1].getPostCtx();
369 // FIXME: disabled following line to fix problem with not updating offsets of pseudo-tokens (context only)
370 //if (newCtx == null && tokens[i].ctx == null) break;
371 tokens[i].setCtx(newCtx,model);
372 i++;
373 }
374 //*/
375 final int ctxMeet = i;
376
377
378 if (Acl2sPlugin.DEBUG) {
379 int prevEnd = 0;
380 LispToken postCtx = null;
381 for (i = 0; i < size; i++) {
382 if (tokens[i].absoluteStart() != prevEnd) {
383 LispToken thisOne = tokens[i];
384 LispToken prevOne = (i > 0) ? tokens[i-1] : null;
385 throw new IllegalStateException("" + thisOne.absoluteStart() + " != " + prevOne.absoluteEnd());
386 }
387 if (tokens[i].absoluteEnd() > docLen) {
388 LispToken thisOne = tokens[i];
389 LispToken prevOne = (i > 0) ? tokens[i-1] : null;
390 throw new IllegalStateException("" + thisOne.absoluteStart() + " != " + prevOne.absoluteEnd());
391 }
392 prevEnd = tokens[i].absoluteEnd();
393 if (!Misc.equal(tokens[i].ctx,postCtx)) {
394 LispToken ctx = tokens[i].ctx;
395 throw new IllegalStateException("Contexts differ " + ctx);
396 }
397 postCtx = tokens[i].getPostCtx();
398 }
399 }
400
401 tokenq.clear();
402 setSizePow2();
403
404 //System.err.println(System.currentTimeMillis()-stime);
405
406 for (TokenListener l : listeners) {
407 l.tokenizationChanged(doc,this,firstIdxInRegion,oldLen,newLen,ctxMeet-firstIdxInRegion);
408 }
409 }
410
411 public IRegion getLastDamageRegion() {
412 return damage;
413 }
414
415 public int getDocLength() {
416 if (tokens[size-1].type != LispToken.EOF_TOK) {
417 throw new IllegalStateException();
418 }
419 return tokens[size-1].offset;
420 }
421
422 public int formSearch(int offset, boolean fwd) {
423 int lastForm = 0;
424 boolean inKeyword = false;
425 for (int i = 0; i < size; i++) {
426 LispToken tok = tokens[i];
427 if (tok.ctx == null) {
428 if (inKeyword && tok.hasWhitespaceNewline()) {
429 inKeyword = false;
430 }
431 if (!inKeyword) {
432 if (!fwd && tok.offset >= offset) break;
433 lastForm = tok.offset;
434 if (fwd && lastForm > offset) break;
435 }
436 try {
437 if (tok.type == LispToken.SYM_TOK && doc.getChar(tok.offset) == ':') {
438 inKeyword = true;
439 }
440 } catch (BadLocationException e) {
441 // won't happen
442 }
443 }
444 }
445 return lastForm;
446 }
447
448 public int tokenSearch(int offset, boolean fwd) {
449 int tIdx;
450
451 if (fwd) {
452 if (offset >= doc.getLength()) return doc.getLength();
453 tIdx = getTokenIdxContaining(offset);
454 if (tokens[tIdx].offset <= offset) {
455 tIdx++;
456 }
457 } else {
458 if (offset <= 0) return 0;
459 tIdx = getTokenIdxContaining(offset-1);
460 if (offset <= tokens[tIdx].offset) {
461 tIdx--;
462 }
463 }
464
465 if (tIdx < 0) {
466 return 0;
467 } else if (tIdx >= size) {
468 return doc.getLength();
469 } else {
470 return tokens[tIdx].offset;
471 }
472 }
473
474 public int nextSexpToken(int offset) {
475 if (offset >= doc.getLength()) return -1;
476 int tIdx = getTokenIdxContaining(offset);
477 LispToken ctx = tokens[tIdx].ctx;
478 int ctxOff = LispToken.contextOffset(ctx);
479
480 int endIdx = tIdx + 1;
481 while (endIdx < size &&
482 LispToken.contextOffset(tokens[endIdx].ctx) != ctxOff &&
483 LispToken.meetContexts(ctx,tokens[endIdx].ctx) == ctx) endIdx++;
484
485 if (endIdx >= size) {
486 return -1;
487 } else {
488 return endIdx;
489 }
490 }
491
492 public int nextSexp(int offset) {
493 int ind = nextSexpToken(offset);
494 return (ind < 0) ? doc.getLength() : tokens[ind].absoluteStart();
495 }
496
497 public int prevSexpToken(int offset) {
498 int tIdx;
499 if (offset >= doc.getLength()) {
500 tIdx = size - 1;
501 } else {
502 tIdx = getTokenIdxContaining(offset);
503 }
504
505 LispToken ctx = tokens[tIdx].ctx;
506 int ctxOff = LispToken.contextOffset(ctx);
507
508 int startIdx;
509 if (offset <= tokens[tIdx].offset) {
510 startIdx = tIdx - 1;
511 } else {
512 return tIdx; // go to beginning of token
513 }
514 while (startIdx >= 0 &&
515 LispToken.contextOffset(tokens[startIdx].ctx) != ctxOff &&
516 LispToken.meetContexts(ctx,tokens[startIdx].ctx) == ctx) startIdx--;
517
518 if (startIdx < 0) {
519 return -1;
520 } else {
521 return startIdx;
522 }
523 }
524
525 public int prevSexp(int offset) {
526 int ind = prevSexpToken(offset);
527 return (ind < 0) ? 0 : tokens[ind].offset;
528 }
529
530 public int pastBeginBookForm() {
531 boolean inKeyword = false;
532 boolean beginBookSeen = false;
533 for (int i = 0; i < size; i++) {
534 LispToken tok = tokens[i];
535 if (tok.ctx == null) {
536 if (beginBookSeen) {
537 return tok.absoluteStart();
538 }
539 if (inKeyword && tok.hasWhitespaceNewline()) {
540 inKeyword = false;
541 }
542 do {
543 if (inKeyword || i + 2 >= size) break;
544 if (!tok.isOParen()) break;
545 LispToken tok2 = tokens[i+1];
546 Sym fn = tok2.asSym(dp);
547 if (fn == null) break; // not a symbol
548 if (!fn.sym.equals("BEGIN-BOOK")) break;
549 beginBookSeen = true;
550 } while (false); // for break;
551 try {
552 if (tok.type == LispToken.SYM_TOK && doc.getChar(tok.offset) == ':') {
553 inKeyword = true;
554 }
555 } catch (BadLocationException e) {
556 // won't happen
557 }
558 }
559 }
560 return -1;
561 }
562
563 public IRegion expandSelection(IRegion existing) {
564 int tokFirst;
565 int tokLast;
566
567 int off = existing.getOffset();
568 int preOff = off == 0 ? 0 : off - 1;
569 int len = existing.getLength();
570 int post = off + len;
571
572 if (len == 0) { // if single point
573 tokFirst = getTokenIdxContaining(preOff);
574 tokLast = getTokenIdxContaining(off);
575 if (tokLast > tokFirst) { // we need to chose just one
576 if (tokens[tokLast].offset > off || // whitespace to the right
577 tokens[tokLast].type == LispToken.EOF_TOK) { // or end of file
578 tokLast = tokFirst;
579 } else {
580 tokFirst = tokLast;
581 }
582 }
583 } else {
584 tokFirst = getTokenIdxContaining(off);
585 tokLast = getTokenIdxContaining(post - 1);
586 }
587
588 LispToken ctx = LispToken.meetContexts(tokens[tokFirst],tokens[tokLast]);
589 if (ctx == null) return existing;
590
591 IRegion r = getCtxSpan(ctx);
592 int rOff = r.getOffset();
593 int rPost = rOff + r.getLength();
594
595 if (rOff < off || rPost > post) {
596 return r; // expand to fill current context;
597 }
598 // otherwise, go up one level (...probably)
599 if (ctx.ctx == null) {
600 // let's not expand to fill the whole file
601 // instead, make sure whitespace is also included
602 rOff = ctx.absoluteStart();
603 return new Region(rOff,rPost-rOff);
604 }
605
606 // yes, go up one level
607 return getCtxSpan(ctx.ctx);
608 }
609
610 public IRegion getCtxSpan(LispToken ctx) {
611 int ctxOff = LispToken.contextOffset(ctx);
612 int startIdx = getTokenIdxContaining(ctxOff);
613 int pastIdx = startIdx + 1;
614 while (pastIdx < size && tokens[pastIdx].hasContextOffset(ctxOff)) pastIdx++;
615 int offset = tokens[startIdx].offset;
616 int length = tokens[pastIdx-1].absoluteEnd() - offset;
617 return new Region(offset, length);
618 }
619
620 public void lowercaseFromOffset(int offset) {
621 int tokIdx = getTokenIdxContaining(offset);
622 LispToken tok = getToken(tokIdx);
623 lowercaseRegion(getCtxSpan(tok));
624 }
625
626 public void lowercaseRegion(IRegion sel) {
627 if (sel.getLength() < 1) throw new IllegalArgumentException("Only works on non-zero length.");
628
629 int tokIdxStart = getTokenIdxContaining(sel.getOffset());
630 int tokIdxEnd = getTokenIdxContaining(sel.getOffset() + sel.getLength() - 1);
631
632 // TODO: make undoable with a single Ctrl+Z
633 for (int cur = tokIdxStart; cur <= tokIdxEnd; cur++) {
634 try {
635 tokens[cur].toLowercase(doc);
636 } catch (BadLocationException e) {
637 // shouldn't happen
638 }
639 }
640 }
641
642
643 // *********** LISTENER STUFF **************
644
645 protected final ArrayList<TokenListener> listeners = new ArrayList<TokenListener>();
646 public void addTokenListener(TokenListener l) {
647 listeners.add(l);
648 }
649
650 public void removeTokenListener(TokenListener l) {
651 listeners.remove(l);
652 }
653
654 public static interface TokenListener {
655 void tokenizationChanged(IDocument doc, LispTokens tokens, int start, int oldLen, int newLen, int ctxLen);
656 }
657
658
659 protected final ArrayList<TokenInvalidationListener> invalidationListeners = new ArrayList<TokenInvalidationListener>();
660 public void addTokenInvalidationListener(TokenInvalidationListener l) {
661 invalidationListeners.add(l);
662 }
663
664 public void removeTokenInvalidationListener(TokenInvalidationListener l) {
665 invalidationListeners.remove(l);
666 }
667
668 public static interface TokenInvalidationListener {
669 void tokenAboutToBeInvalidated(IDocument doc, LispTokens tokens, LispToken target);
670 }
671
672 }