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  	}