PageRenderTime 58ms CodeModel.GetById 26ms app.highlight 26ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-0-pre5/org/gjt/sp/jedit/syntax/TokenMarker.java

#
Java | 896 lines | 647 code | 114 blank | 135 comment | 163 complexity | 8a7536a95426b58b02378857f94e2928 MD5 | raw file
  1/*
  2 * TokenMarker.java - Tokenizes lines of text
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 1998, 1999, 2000, 2001 Slava Pestov
  7 * Copyright (C) 1999, 2000 mike dillon
  8 *
  9 * This program is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU General Public License
 11 * as published by the Free Software Foundation; either version 2
 12 * of the License, or any later version.
 13 *
 14 * This program is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17 * GNU General Public License for more details.
 18 *
 19 * You should have received a copy of the GNU General Public License
 20 * along with this program; if not, write to the Free Software
 21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 22 */
 23
 24package org.gjt.sp.jedit.syntax;
 25
 26//{{{ Imports
 27import javax.swing.text.Segment;
 28import java.util.*;
 29import org.gjt.sp.jedit.*;
 30import org.gjt.sp.util.Log;
 31//}}}
 32
 33/**
 34 * A token marker splits lines of text into tokens. Each token carries
 35 * a length field and an identification tag that can be mapped to a color
 36 * or font style for painting that token.
 37 *
 38 * @author Slava Pestov, mike dillon
 39 * @version $Id: TokenMarker.java 3954 2001-12-30 07:35:03Z spestov $
 40 *
 41 * @see org.gjt.sp.jedit.syntax.Token
 42 */
 43public class TokenMarker
 44{
 45	//{{{ Major actions (total: 8)
 46	public static final int MAJOR_ACTIONS = 0x000000FF;
 47	public static final int WHITESPACE = 1 << 0;
 48	public static final int SPAN = 1 << 1;
 49	public static final int MARK_PREVIOUS = 1 << 2;
 50	public static final int MARK_FOLLOWING = 1 << 3;
 51	public static final int EOL_SPAN = 1 << 4;
 52//	public static final int MAJOR_ACTION_5 = 1 << 5;
 53//	public static final int MAJOR_ACTION_6 = 1 << 6;
 54//	public static final int MAJOR_ACTION_7 = 1 << 7;
 55	//}}}
 56
 57	//{{{ Action hints (total: 8)
 58	public static final int ACTION_HINTS = 0x0000FF00;
 59	public static final int EXCLUDE_MATCH = 1 << 8;
 60	public static final int AT_LINE_START = 1 << 9;
 61	public static final int NO_LINE_BREAK = 1 << 10;
 62	public static final int NO_WORD_BREAK = 1 << 11;
 63	public static final int IS_ESCAPE = 1 << 12;
 64	public static final int DELEGATE = 1 << 13;
 65//	public static final int ACTION_HINT_14 = 1 << 14;
 66//	public static final int ACTION_HINT_15 = 1 << 15;
 67	//}}}
 68
 69	//{{{ TokenMarker constructor
 70	public TokenMarker()
 71	{
 72		ruleSets = new Hashtable(64);
 73	} //}}}
 74
 75	//{{{ addRuleSet() method
 76	public void addRuleSet(String setName, ParserRuleSet rules)
 77	{
 78		if (rules == null) return;
 79
 80		if (setName == null) setName = "MAIN";
 81
 82		ruleSets.put(rulePfx.concat(setName), rules);
 83
 84		if (setName.equals("MAIN"))
 85			mainRuleSet = rules;
 86	} //}}}
 87
 88	//{{{ getMainRuleSet() method
 89	public ParserRuleSet getMainRuleSet()
 90	{
 91		return mainRuleSet;
 92	} //}}}
 93
 94	//{{{ getRuleSet() method
 95	public ParserRuleSet getRuleSet(String setName)
 96	{
 97		ParserRuleSet rules;
 98
 99		rules = (ParserRuleSet) ruleSets.get(setName);
100
101		if (rules == null && !setName.startsWith(rulePfx))
102		{
103			int delim = setName.indexOf("::");
104
105			String modeName = setName.substring(0, delim);
106
107			Mode mode = jEdit.getMode(modeName);
108			if(mode == null)
109			{
110				Log.log(Log.ERROR,TokenMarker.class,
111					"Unknown edit mode: " + modeName);
112				rules = null;
113			}
114			else
115			{
116				TokenMarker marker = mode.getTokenMarker();
117				rules = marker.getRuleSet(setName);
118			}
119
120			// store external ParserRuleSet in the local hashtable for
121			// faster lookups later
122			ruleSets.put(setName, rules);
123		}
124
125		if (rules == null)
126		{
127			Log.log(Log.ERROR,this,"Unresolved delegate target: " + setName);
128		}
129
130		return rules;
131	} //}}}
132
133	//{{{ getName() method
134	public String getName()
135	{
136		return name;
137	} //}}}
138
139	//{{{ setName() method
140	public void setName(String name)
141	{
142		if (name == null) throw new NullPointerException();
143
144		this.name = name;
145		rulePfx = name.concat("::");
146	} //}}}
147
148	//{{{ markTokens() method
149	/**
150	 * Do not call this method directly; call Buffer.markTokens() instead.
151	 */
152	public LineContext markTokens(LineContext prevContext,
153		Buffer.TokenList tokenList, Segment line)
154	{
155		this.context = new LineContext();
156
157		//{{{ Set up context
158		if(prevContext == null)
159			context.rules = getMainRuleSet();
160		else
161		{
162			context.parent = prevContext.parent;
163			context.inRule = prevContext.inRule;
164			context.rules = prevContext.rules;
165		} //}}}
166
167		lastOffset = lastKeyword = line.offset;
168		lineLength = line.count + line.offset;
169
170		int terminateChar = context.rules.getTerminateChar();
171		int searchLimit = (terminateChar >= 0 && terminateChar < line.count)
172			? line.offset + terminateChar : lineLength;
173
174		escaped = false;
175
176		boolean b;
177		boolean tempEscaped;
178		Segment tempPattern;
179		ParserRule rule;
180		LineContext tempContext;
181
182		for(pos = line.offset; pos < searchLimit; pos++)
183		{
184			//{{{ if we are not in the top level context, we are delegated
185			if (context.parent != null)
186			{
187				tempContext = context;
188
189				context = context.parent;
190
191				pattern.array = context.inRule.searchChars;
192				pattern.count = context.inRule.sequenceLengths[1];
193				pattern.offset = context.inRule.sequenceLengths[0];
194
195				b = handleRule(tokenList, line, context.inRule);
196
197				context = tempContext;
198
199				if (!b)
200				{
201					if (escaped)
202					{
203						escaped = false;
204					}
205					else
206					{
207						if (pos != lastOffset)
208						{
209							if (context.inRule == null)
210							{
211								markKeyword(tokenList,line,lastKeyword,pos);
212
213								tokenList.addToken(pos - lastOffset,
214									context.rules.getDefault(),
215									context.rules);
216							}
217							else if ((context.inRule.action & (NO_LINE_BREAK | NO_WORD_BREAK)) == 0)
218							{
219								tokenList.addToken(pos - lastOffset,
220									context.inRule.token,
221									context.rules);
222							}
223							else
224							{
225								tokenList.addToken(pos - lastOffset, Token.INVALID,
226									context.rules);
227							}
228						}
229
230						context = (LineContext)context.parent.clone();
231
232						if ((context.inRule.action & EXCLUDE_MATCH) == EXCLUDE_MATCH)
233						{
234							tokenList.addToken(pattern.count,
235								context.rules.getDefault(),
236								context.rules);
237						}
238						else
239						{
240							tokenList.addToken(pattern.count,
241								context.inRule.token,
242								context.rules);
243						}
244
245						context.inRule = null;
246
247						lastKeyword = lastOffset = pos + pattern.count;
248					}
249
250					pos += (pattern.count - 1); // move pos to last character of match sequence
251
252					continue;
253				}
254			} //}}}
255
256			//{{{ check the escape rule for the current context, if there is one
257			if ((rule = context.rules.getEscapeRule()) != null)
258			{
259				// assign tempPattern to mutable "buffer" pattern
260				tempPattern = pattern;
261
262				// swap in the escape pattern
263				pattern = context.rules.getEscapePattern();
264
265				tempEscaped = escaped;
266
267				b = handleRule(tokenList, line, rule);
268
269				// swap back the buffer pattern
270				pattern = tempPattern;
271
272				if (!b)
273				{
274					if (tempEscaped) escaped = false;
275					continue;
276				}
277			} //}}}
278
279			//{{{ if we are inside a span, check for its end sequence
280			rule = context.inRule;
281			if(rule != null && (rule.action & SPAN) == SPAN)
282			{
283				pattern.array = rule.searchChars;
284				pattern.count = rule.sequenceLengths[1];
285				pattern.offset = rule.sequenceLengths[0];
286
287				// if we match the end of the span, or if this is a "hard" span,
288				// we continue to the next character; otherwise, we check all
289				// applicable rules below
290				if (!handleRule(tokenList,line,rule)
291					|| (rule.action & SOFT_SPAN) == 0)
292				{
293					escaped = false;
294					continue;
295				}
296			} //}}}
297
298			//{{{ now check every rule
299			rule = context.rules.getRules(line.array[pos]);
300			while(rule != null)
301			{
302				pattern.array = rule.searchChars;
303
304				if (context.inRule == rule && (rule.action & SPAN) == SPAN)
305				{
306					pattern.count = rule.sequenceLengths[1];
307					pattern.offset = rule.sequenceLengths[0];
308				}
309				else
310				{
311					pattern.count = rule.sequenceLengths[0];
312					pattern.offset = 0;
313				}
314
315				// stop checking rules if there was a match and go to next pos
316				if (!handleRule(tokenList,line,rule))
317					break;
318
319				rule = rule.next;
320			} //}}}
321
322			escaped = false;
323		}
324
325		//{{{ check for keywords at the line's end
326		if(context.inRule == null)
327			markKeyword(tokenList, line, lastKeyword, lineLength);
328		//}}}
329
330		//{{{ mark all remaining characters
331		if(lastOffset != lineLength)
332		{
333			if (context.inRule == null)
334			{
335				tokenList.addToken(lineLength - lastOffset,
336					context.rules.getDefault(),
337					context.rules);
338			}
339			else if (
340				(context.inRule.action & SPAN) == SPAN &&
341				(context.inRule.action & (NO_LINE_BREAK | NO_WORD_BREAK)) != 0
342			)
343			{
344				tokenList.addToken(lineLength - lastOffset,Token.INVALID,
345					context.rules);
346				context.inRule = null;
347			}
348			else
349			{
350				tokenList.addToken(lineLength - lastOffset,
351					context.inRule.token,
352					context.rules);
353
354				if((context.inRule.action & MARK_FOLLOWING) == MARK_FOLLOWING)
355				{
356					context.inRule = null;
357				}
358			}
359		} //}}}
360
361		tokenList.addToken(0,Token.END,context.rules);
362
363		return context.intern();
364	} //}}}
365
366	//{{{ Private members
367	private static final int SOFT_SPAN = MARK_FOLLOWING | NO_WORD_BREAK;
368
369	//{{{ Instance variables
370	private String name;
371	private String rulePfx;
372	private Hashtable ruleSets;
373	private ParserRuleSet mainRuleSet;
374
375	private LineContext context;
376	private Segment pattern = new Segment(new char[0],0,0);
377	private int lastOffset;
378	private int lastKeyword;
379	private int lineLength;
380	private int pos;
381	private boolean escaped;
382	//}}}
383
384	//{{{ handleRule() method
385	/**
386	 * Checks if the rule matches the line at the current position
387	 * and handles the rule if it does match
388	 * @param line Segment to check rule against
389	 * @param checkRule ParserRule to check against line
390	 * @return true,  keep checking other rules
391	 *     <br>false, stop checking other rules
392	 */
393	private boolean handleRule(Buffer.TokenList tokenList, Segment line,
394		ParserRule checkRule)
395	{
396		if (pattern.count == 0) return true;
397
398		if (lineLength - pos < pattern.count) return true;
399
400		char a, b;
401		for (int k = 0; k < pattern.count; k++)
402		{
403			a = pattern.array[pattern.offset + k];
404			b = line.array[pos + k];
405
406			//{{{ break out and check the next rule if there is a mismatch
407			if (
408				!(
409					a == b ||
410					context.rules.getIgnoreCase() &&
411					(
412						Character.toLowerCase(a) == b ||
413						a == Character.toLowerCase(b)
414					)
415				)
416			) return true;
417			//}}}
418		}
419
420		if (escaped)
421		{
422			pos += pattern.count - 1;
423			return false;
424		}
425		else if ((checkRule.action & IS_ESCAPE) == IS_ESCAPE)
426		{
427			escaped = true;
428			pos += pattern.count - 1;
429			return false;
430		}
431
432		//{{{ handle soft spans
433		if (context.inRule != checkRule && context.inRule != null
434			&& (context.inRule.action & SOFT_SPAN) != 0)
435		{
436			if ((context.inRule.action & NO_WORD_BREAK) == NO_WORD_BREAK)
437			{
438				tokenList.addToken(pos - lastOffset, Token.INVALID,
439					context.rules);
440			}
441			else
442			{
443				tokenList.addToken(pos - lastOffset,
444					context.inRule.token,
445					context.rules);
446			}
447			lastOffset = lastKeyword = pos;
448			context.inRule = null;
449		} //}}}
450
451		//{{{ not inside a rule
452		if (context.inRule == null)
453		{
454			if ((checkRule.action & AT_LINE_START) == AT_LINE_START)
455			{
456				if (
457					(((checkRule.action & MARK_PREVIOUS) != 0) ?
458					lastKeyword :
459					pos) != line.offset
460				)
461				{
462					return true;
463				}
464			}
465
466			markKeyword(tokenList, line, lastKeyword, pos);
467
468			if ((checkRule.action & MARK_PREVIOUS) != MARK_PREVIOUS)
469			{
470				lastKeyword = pos + pattern.count;
471
472				if ((checkRule.action & WHITESPACE) == WHITESPACE)
473				{
474					return false; // break out of inner for loop to check next char
475				}
476
477				// mark previous sequence as NULL (plain text)
478				if (lastOffset < pos)
479				{
480					tokenList.addToken(pos - lastOffset,
481						context.rules.getDefault(),
482						context.rules);
483				}
484			}
485
486			switch(checkRule.action & MAJOR_ACTIONS)
487			{
488			//{{{ SEQ
489			case 0:
490				// this is a plain sequence rule
491				tokenList.addToken(pattern.count,checkRule.token,
492					context.rules);
493				lastOffset = pos + pattern.count;
494
495				break;
496			//}}}
497			//{{{ SPAN
498			case SPAN:
499				context.inRule = checkRule;
500
501				//{{{ Non-delegated
502				if ((checkRule.action & DELEGATE) != DELEGATE)
503				{
504					if ((checkRule.action & EXCLUDE_MATCH) == EXCLUDE_MATCH)
505					{
506						tokenList.addToken(pattern.count,
507							context.rules.getDefault(),
508							context.rules);
509						lastOffset = pos + pattern.count;
510					}
511					else
512					{
513						lastOffset = pos;
514					}
515				} //}}}
516				//{{{ Delegated
517				else
518				{
519					String setName = new String(checkRule.searchChars,
520						checkRule.sequenceLengths[0] + checkRule.sequenceLengths[1],
521						checkRule.sequenceLengths[2]);
522
523					ParserRuleSet delegateSet = getRuleSet(setName);
524
525					if (delegateSet != null)
526					{
527						if ((checkRule.action & EXCLUDE_MATCH) == EXCLUDE_MATCH)
528						{
529							tokenList.addToken(pattern.count,
530								context.rules.getDefault(),
531								context.rules);
532						}
533						else
534						{
535							tokenList.addToken(pattern.count,
536								checkRule.token,
537								context.rules);
538						}
539						lastOffset = pos + pattern.count;
540
541						context = new LineContext(delegateSet, context);
542					}
543				} //}}}
544
545				break;
546			//}}}
547			//{{{ EOL_SPAN
548			case EOL_SPAN:
549				if ((checkRule.action & EXCLUDE_MATCH) == EXCLUDE_MATCH)
550				{
551					tokenList.addToken(pattern.count,
552						context.rules.getDefault(),
553						context.rules);
554					tokenList.addToken(lineLength - (pos + pattern.count),
555						checkRule.token,context.rules);
556				}
557				else
558				{
559					tokenList.addToken(lineLength - pos,
560						checkRule.token,context.rules);
561				}
562				lastOffset = lineLength;
563				lastKeyword = lineLength;
564				pos = lineLength;
565
566				return false;
567			//}}}
568			//{{{ MARK_PREVIOUS
569			case MARK_PREVIOUS:
570				if (lastKeyword > lastOffset)
571				{
572					tokenList.addToken(lastKeyword - lastOffset,
573						context.rules.getDefault(),
574						context.rules);
575					lastOffset = lastKeyword;
576				}
577
578				if ((checkRule.action & EXCLUDE_MATCH) == EXCLUDE_MATCH)
579				{
580					tokenList.addToken(pos - lastOffset,
581						checkRule.token,context.rules);
582					tokenList.addToken(pattern.count,
583						context.rules.getDefault(),
584						context.rules);
585				}
586				else
587				{
588					tokenList.addToken(pos - lastOffset + pattern.count,
589						checkRule.token,context.rules);
590				}
591				lastOffset = pos + pattern.count;
592
593				break;
594			//}}}
595			//{{{ MARK_FOLLOWING
596			case MARK_FOLLOWING:
597				context.inRule = checkRule;
598				if ((checkRule.action & EXCLUDE_MATCH) == EXCLUDE_MATCH)
599				{
600					tokenList.addToken(pattern.count,
601						context.rules.getDefault(),
602						context.rules);
603					lastOffset = pos + pattern.count;
604				}
605				else
606				{
607					lastOffset = pos;
608				}
609
610				break;
611			//}}}
612			default:
613				throw new InternalError("Unhandled major action");
614			}
615
616			lastKeyword = lastOffset;
617
618			pos += (pattern.count - 1); // move pos to last character of match sequence
619			return false; // break out of inner for loop to check next char
620		}
621		//}}}
622		//{{{ inside a SPAN
623		else if ((checkRule.action & SPAN) == SPAN)
624		{
625			if ((checkRule.action & DELEGATE) != DELEGATE)
626			{
627				context.inRule = null;
628				if ((checkRule.action & EXCLUDE_MATCH) == EXCLUDE_MATCH)
629				{
630					tokenList.addToken(pos - lastOffset,
631						checkRule.token,context.rules);
632					tokenList.addToken(pattern.count,
633						context.rules.getDefault(),
634						context.rules);
635				}
636				else
637				{
638					tokenList.addToken((pos + pattern.count) - lastOffset,
639						checkRule.token,context.rules);
640				}
641				lastKeyword = lastOffset = pos + pattern.count;
642
643				pos += (pattern.count - 1); // move pos to last character of match sequence
644			}
645
646			return false; // break out of inner for loop to check next char
647		}//}}}
648
649		return true;
650	} //}}}
651
652	//{{{ markKeyword() method
653	private void markKeyword(Buffer.TokenList tokenList, Segment line,
654		int start, int end)
655	{
656		KeywordMap keywords = context.rules.getKeywords();
657
658		int len = end - start;
659
660		//{{{ do digits.
661
662		/* right now, this is hardcoded to handle these cases:
663		 * 1234
664		 * 0x1234abcf
665		 * 1234l
666		 * 12.34f
667		 * 12.34d
668		 *
669		 * in the future, we need some sort of regexp mechanism. */
670		if(context.rules.getHighlightDigits())
671		{
672			boolean digit = true;
673			char[] array = line.array;
674			boolean octal = false;
675			boolean hex = false;
676			boolean seenSomeDigits = false;
677loop:			for(int i = 0; i < len; i++)
678			{
679				char ch = array[start+i];
680				switch(ch)
681				{
682				case '0':
683					if(i == 0)
684						octal = true;
685					seenSomeDigits = true;
686					continue loop;
687				case '1': case '2': case '3':
688				case '4': case '5': case '6':
689				case '7': case '8': case '9':
690					seenSomeDigits = true;
691					continue loop;
692				case 'x': case 'X':
693					if(octal && i == 1)
694					{
695						hex = true;
696						continue loop;
697					}
698					else
699						break;
700				case 'd': case 'D':
701				case 'f': case 'F':
702					if(hex)
703						continue loop;
704					else if(i == len -1 && seenSomeDigits)
705						continue loop;
706					else
707						break;
708				case 'l': case 'L':
709					if(i == len -1 && seenSomeDigits)
710						continue loop;
711					else
712						break;
713				case 'e': case 'E':
714					if(seenSomeDigits)
715						continue loop;
716					else
717						break;
718				case 'a': case 'A': case 'b': case 'B':
719				case 'c': case 'C':
720					if(hex)
721						continue loop;
722					else
723						break;
724				case '.': case '-':
725					// normally, this shouldn't be
726					// necessary, because most modes
727					// define '.' and '-' SEQs. However,
728					// in props mode, we can't define
729					// such a SEQ because it would
730					// break the AT_LINE_START
731					// MARK_PREVIOUS rule.
732
733					continue loop;
734				default:
735					break;
736				}
737
738				// if we ended up here, then we have found a
739				// non-digit character.
740				digit = false;
741				break loop;
742			}
743
744			// if we got this far with digit = true, then the keyword
745			// consists of all digits. Add it as such.
746			if(digit && seenSomeDigits)
747			{
748				if(start != lastOffset)
749				{
750					tokenList.addToken(start - lastOffset,
751						context.rules.getDefault(),
752						context.rules);
753				}
754				tokenList.addToken(len,Token.DIGIT,context.rules);
755				lastKeyword = lastOffset = end;
756
757				return;
758			}
759		} //}}}
760
761		if(keywords != null)
762		{
763			byte id = keywords.lookup(line, start, len);
764
765			if(id != Token.NULL)
766			{
767				if(start != lastOffset)
768				{
769					tokenList.addToken(start - lastOffset,
770						context.rules.getDefault(),
771						context.rules);
772				}
773				tokenList.addToken(len,id,context.rules);
774				lastKeyword = lastOffset = end;
775			}
776		}
777	} //}}}
778
779	//}}}
780
781	//{{{ LineContext class
782	public static class LineContext
783	{
784		//{{{ Debug code
785		static int count;
786		static int countGC;
787
788		public String getAllocationStatistics()
789		{
790			return "total: " + count + ", in core: " +
791				(count - countGC)
792				+ ", interned: " + intern.size();
793		} //}}}
794
795		static Hashtable intern = new Hashtable();
796
797		public LineContext parent;
798		public ParserRule inRule;
799		public ParserRuleSet rules;
800
801		//{{{ LineContext constructor
802		public LineContext(ParserRule r, ParserRuleSet rs)
803		{
804			this();
805			inRule = r;
806			rules = rs;
807		} //}}}
808
809		//{{{ LineContext constructor
810		public LineContext(ParserRuleSet rs, LineContext lc)
811		{
812			this();
813			rules = rs;
814			parent = (lc == null ? null : (LineContext)lc.clone());
815		} //}}}
816
817		//{{{ LineContext constructor
818		public LineContext(ParserRule r)
819		{
820			this();
821			inRule = r;
822		} //}}}
823
824		//{{{ LineContext constructor
825		public LineContext()
826		{
827			count++;
828		} //}}}
829
830		//{{{ intern() method
831		public LineContext intern()
832		{
833			Object obj = intern.get(this);
834			if(obj == null)
835			{
836				intern.put(this,this);
837				return this;
838			}
839			else
840				return (LineContext)obj;
841		} //}}}
842
843		//{{{ finalize() method
844		public void finalize()
845		{
846			countGC++;
847		} //}}}
848
849		//{{{ hashCode() method
850		public int hashCode()
851		{
852			if(inRule != null)
853				return inRule.hashCode();
854			else if(rules != null)
855				return rules.hashCode();
856			else
857				return 0;
858		} //}}}
859
860		//{{{ equals() method
861		public boolean equals(Object obj)
862		{
863			if(obj instanceof LineContext)
864			{
865				LineContext lc = (LineContext)obj;
866				if(lc.parent == null)
867				{
868					if(parent != null)
869						return false;
870				}
871				else //if(lc.parent != null)
872				{
873					if(parent == null)
874						return false;
875					else if(!lc.parent.equals(parent))
876						return false;
877				}
878
879				return lc.inRule == inRule && lc.rules == rules;
880			}
881			else
882				return false;
883		} //}}}
884
885		//{{{ clone() method
886		public Object clone()
887		{
888			LineContext lc = new LineContext();
889			lc.inRule = inRule;
890			lc.rules = rules;
891			lc.parent = (parent == null) ? null : (LineContext) parent.clone();
892
893			return lc;
894		} //}}}
895	} //}}}
896}