001: /*
002: * XModeHandler.java - XML handler for mode files
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 1999 mike dillon
007: * Portions copyright (C) 2000, 2001 Slava Pestov
008: *
009: * This program is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; either version 2
012: * of the License, or any later version.
013: *
014: * This program is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017: * GNU General Public License for more details.
018: *
019: * You should have received a copy of the GNU General Public License
020: * along with this program; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
022: */
023:
024: package org.gjt.sp.jedit.syntax;
025:
026: //{{{ Imports
027: import java.util.*;
028: import java.util.regex.Pattern;
029: import java.util.regex.PatternSyntaxException;
030:
031: import org.xml.sax.Attributes;
032: import org.xml.sax.InputSource;
033: import org.xml.sax.helpers.DefaultHandler;
034:
035: import org.gjt.sp.jedit.jEdit;
036: import org.gjt.sp.jedit.Mode;
037: import org.gjt.sp.util.Log;
038: import org.gjt.sp.util.XMLUtilities;
039:
040: //}}}
041:
042: /**
043: * XML handler for mode definition files.
044: * @version $Id: XModeHandler.java 11079 2007-11-16 03:20:00Z vanza $
045: */
046: public abstract class XModeHandler extends DefaultHandler {
047: //{{{ XModeHandler constructor
048: public XModeHandler(String modeName) {
049: this .modeName = modeName;
050: marker = new TokenMarker();
051: marker.addRuleSet(new ParserRuleSet(modeName, "MAIN"));
052: stateStack = new Stack<TagDecl>();
053: } //}}}
054:
055: //{{{ resolveEntity() method
056: public InputSource resolveEntity(String publicId, String systemId) {
057: return XMLUtilities.findEntity(systemId, "xmode.dtd",
058: XModeHandler.class);
059: } //}}}
060:
061: //{{{ characters() method
062: public void characters(char[] c, int off, int len) {
063: peekElement().setText(c, off, len);
064: } //}}}
065:
066: //{{{ startElement() method
067: public void startElement(String uri, String localName,
068: String qName, Attributes attrs) {
069: TagDecl tag = pushElement(qName, attrs);
070:
071: if (qName.equals("WHITESPACE")) {
072: Log.log(Log.WARNING, this , modeName + ": WHITESPACE rule "
073: + "no longer needed");
074: } else if (qName.equals("KEYWORDS")) {
075: keywords = new KeywordMap(rules.getIgnoreCase());
076: } else if (qName.equals("RULES")) {
077: if (tag.lastSetName == null)
078: tag.lastSetName = "MAIN";
079: rules = marker.getRuleSet(tag.lastSetName);
080: if (rules == null) {
081: rules = new ParserRuleSet(modeName, tag.lastSetName);
082: marker.addRuleSet(rules);
083: }
084: rules.setIgnoreCase(tag.lastIgnoreCase);
085: rules.setHighlightDigits(tag.lastHighlightDigits);
086: if (tag.lastDigitRE != null) {
087: try {
088: rules
089: .setDigitRegexp(Pattern
090: .compile(
091: tag.lastDigitRE,
092: tag.lastIgnoreCase ? Pattern.CASE_INSENSITIVE
093: : 0));
094: } catch (PatternSyntaxException e) {
095: error("regexp", e);
096: }
097: }
098:
099: if (tag.lastEscape != null)
100: rules.setEscapeRule(ParserRule
101: .createEscapeRule(tag.lastEscape));
102: rules.setDefault(tag.lastDefaultID);
103: rules.setNoWordSep(tag.lastNoWordSep);
104: }
105: } //}}}
106:
107: //{{{ endElement() method
108: public void endElement(String uri, String localName, String name) {
109: TagDecl tag = popElement();
110: if (name.equals(tag.tagName)) {
111: if (tag.lastDelegateSet != null
112: && !tag.tagName.equals("IMPORT")
113: && !tag.lastDelegateSet.getModeName().equals(
114: modeName)) {
115: Mode mode = jEdit.getMode(tag.lastDelegateSet
116: .getModeName());
117: if (!reloadModes.contains(mode)) {
118: reloadModes.add(mode);
119: }
120: }
121: //{{{ PROPERTY
122: if (tag.tagName.equals("PROPERTY")) {
123: props.put(propName, propValue);
124: } //}}}
125: //{{{ PROPS
126: else if (tag.tagName.equals("PROPS")) {
127: if (peekElement().tagName.equals("RULES"))
128: rules.setProperties(props);
129: else
130: modeProps = props;
131:
132: props = new Hashtable<String, String>();
133: } //}}}
134: //{{{ RULES
135: else if (tag.tagName.equals("RULES")) {
136: rules.setKeywords(keywords);
137: keywords = null;
138: rules = null;
139: } //}}}
140: //{{{ IMPORT
141: else if (tag.tagName.equals("IMPORT")) {
142: // prevent lockups
143: if (!rules.equals(tag.lastDelegateSet)) {
144: rules.addRuleSet(tag.lastDelegateSet);
145: }
146: } //}}}
147: //{{{ TERMINATE
148: else if (tag.tagName.equals("TERMINATE")) {
149: rules.setTerminateChar(tag.termChar);
150: } //}}}
151: //{{{ SEQ
152: else if (tag.tagName.equals("SEQ")) {
153: if (tag.lastStart == null) {
154: error("empty-tag", "SEQ");
155: return;
156: }
157:
158: rules.addRule(ParserRule.createSequenceRule(
159: tag.lastStartPosMatch,
160: tag.lastStart.toString(), tag.lastDelegateSet,
161: tag.lastTokenID));
162: } //}}}
163: //{{{ SEQ_REGEXP
164: else if (tag.tagName.equals("SEQ_REGEXP")) {
165: if (tag.lastStart == null) {
166: error("empty-tag", "SEQ_REGEXP");
167: return;
168: }
169:
170: try {
171: if (null != tag.lastHashChars) {
172: rules
173: .addRule(ParserRule
174: .createRegexpSequenceRule(
175: tag.lastStartPosMatch,
176: tag.lastHashChars
177: .toCharArray(),
178: tag.lastStart
179: .toString(),
180: tag.lastDelegateSet,
181: tag.lastTokenID,
182: findParent("RULES").lastIgnoreCase));
183: } else {
184: rules
185: .addRule(ParserRule
186: .createRegexpSequenceRule(
187: tag.lastHashChar,
188: tag.lastStartPosMatch,
189: tag.lastStart
190: .toString(),
191: tag.lastDelegateSet,
192: tag.lastTokenID,
193: findParent("RULES").lastIgnoreCase));
194: }
195: } catch (PatternSyntaxException re) {
196: error("regexp", re);
197: }
198: } //}}}
199: //{{{ SPAN
200: else if (tag.tagName.equals("SPAN")) {
201: if (tag.lastStart == null) {
202: error("empty-tag", "BEGIN");
203: return;
204: }
205:
206: if (tag.lastEnd == null) {
207: error("empty-tag", "END");
208: return;
209: }
210:
211: rules.addRule(ParserRule.createSpanRule(
212: tag.lastStartPosMatch,
213: tag.lastStart.toString(), tag.lastEndPosMatch,
214: tag.lastEnd.toString(), tag.lastDelegateSet,
215: tag.lastTokenID, tag.lastMatchType,
216: tag.lastNoLineBreak, tag.lastNoWordBreak,
217: tag.lastEscape));
218: } //}}}
219: //{{{ SPAN_REGEXP
220: else if (tag.tagName.equals("SPAN_REGEXP")) {
221: if (tag.lastStart == null) {
222: error("empty-tag", "BEGIN");
223: return;
224: }
225:
226: if (tag.lastEnd == null) {
227: error("empty-tag", "END");
228: return;
229: }
230:
231: try {
232: if (null != tag.lastHashChars) {
233: rules.addRule(ParserRule.createRegexpSpanRule(
234: tag.lastStartPosMatch,
235: tag.lastHashChars.toCharArray(),
236: tag.lastStart.toString(),
237: tag.lastEndPosMatch, tag.lastEnd
238: .toString(),
239: tag.lastDelegateSet, tag.lastTokenID,
240: tag.lastMatchType, tag.lastNoLineBreak,
241: tag.lastNoWordBreak,
242: findParent("RULES").lastIgnoreCase,
243: tag.lastEscape));
244: } else {
245: rules.addRule(ParserRule.createRegexpSpanRule(
246: tag.lastHashChar,
247: tag.lastStartPosMatch, tag.lastStart
248: .toString(),
249: tag.lastEndPosMatch, tag.lastEnd
250: .toString(),
251: tag.lastDelegateSet, tag.lastTokenID,
252: tag.lastMatchType, tag.lastNoLineBreak,
253: tag.lastNoWordBreak,
254: findParent("RULES").lastIgnoreCase,
255: tag.lastEscape));
256: }
257: } catch (PatternSyntaxException re) {
258: error("regexp", re);
259: }
260: } //}}}
261: //{{{ EOL_SPAN
262: else if (tag.tagName.equals("EOL_SPAN")) {
263: if (tag.lastStart == null) {
264: error("empty-tag", "EOL_SPAN");
265: return;
266: }
267:
268: rules.addRule(ParserRule.createEOLSpanRule(
269: tag.lastStartPosMatch,
270: tag.lastStart.toString(), tag.lastDelegateSet,
271: tag.lastTokenID, tag.lastMatchType));
272: } //}}}
273: //{{{ EOL_SPAN_REGEXP
274: else if (tag.tagName.equals("EOL_SPAN_REGEXP")) {
275: if (tag.lastStart == null) {
276: error("empty-tag", "EOL_SPAN_REGEXP");
277: return;
278: }
279:
280: try {
281: if (null != tag.lastHashChars) {
282: rules
283: .addRule(ParserRule
284: .createRegexpEOLSpanRule(
285: tag.lastStartPosMatch,
286: tag.lastHashChars
287: .toCharArray(),
288: tag.lastStart
289: .toString(),
290: tag.lastDelegateSet,
291: tag.lastTokenID,
292: tag.lastMatchType,
293: findParent("RULES").lastIgnoreCase));
294: } else {
295: rules
296: .addRule(ParserRule
297: .createRegexpEOLSpanRule(
298: tag.lastHashChar,
299: tag.lastStartPosMatch,
300: tag.lastStart
301: .toString(),
302: tag.lastDelegateSet,
303: tag.lastTokenID,
304: tag.lastMatchType,
305: findParent("RULES").lastIgnoreCase));
306: }
307: } catch (PatternSyntaxException re) {
308: error("regexp", re);
309: }
310: } //}}}
311: //{{{ MARK_FOLLOWING
312: else if (tag.tagName.equals("MARK_FOLLOWING")) {
313: if (tag.lastStart == null) {
314: error("empty-tag", "MARK_FOLLOWING");
315: return;
316: }
317:
318: rules.addRule(ParserRule.createMarkFollowingRule(
319: tag.lastStartPosMatch,
320: tag.lastStart.toString(), tag.lastTokenID,
321: tag.lastMatchType));
322: } //}}}
323: //{{{ MARK_PREVIOUS
324: else if (tag.tagName.equals("MARK_PREVIOUS")) {
325: if (tag.lastStart == null) {
326: error("empty-tag", "MARK_PREVIOUS");
327: return;
328: }
329:
330: rules.addRule(ParserRule.createMarkPreviousRule(
331: tag.lastStartPosMatch,
332: tag.lastStart.toString(), tag.lastTokenID,
333: tag.lastMatchType));
334: } //}}}
335: //{{{ Keywords
336: else if (!tag.tagName.equals("END")
337: && !tag.tagName.equals("BEGIN")
338: && !tag.tagName.equals("KEYWORDS")
339: && !tag.tagName.equals("MODE")) {
340: byte token = Token.stringToToken(tag.tagName);
341: if (token != -1)
342: addKeyword(tag.lastKeyword.toString(), token);
343: } //}}}
344: } else {
345: // can't happen
346: throw new InternalError();
347: }
348: } //}}}
349:
350: //{{{ startDocument() method
351: public void startDocument() {
352: props = new Hashtable<String, String>();
353: pushElement(null, null);
354: reloadModes = new Vector<Mode>();
355: } //}}}
356:
357: //{{{ endDocument() method
358: public void endDocument() {
359: ParserRuleSet[] rulesets = marker.getRuleSets();
360: for (int i = 0; i < rulesets.length; i++) {
361: rulesets[i].resolveImports();
362: }
363: for (Mode mode : reloadModes) {
364: mode.setTokenMarker(null);
365: mode.loadIfNecessary();
366: }
367: } //}}}
368:
369: //{{{ getTokenMarker() method
370: /**
371: * Returns the TokenMarker.
372: *
373: * @return a TokenMarker it cannot be null
374: */
375: public TokenMarker getTokenMarker() {
376: return marker;
377: } //}}}
378:
379: //{{{ getModeProperties() method
380: public Hashtable<String, String> getModeProperties() {
381: return modeProps;
382: } //}}}
383:
384: //{{{ Protected members
385:
386: //{{{ error() method
387: /**
388: * Reports an error.
389: * You must override this method so that the mode loader can do error
390: * reporting.
391: * @param msg The error type
392: * @param subst A <code>String</code> or a <code>Throwable</code>
393: * containing specific information
394: * @since jEdit 4.2pre1
395: */
396: protected abstract void error(String msg, Object subst);
397:
398: //}}}
399:
400: //{{{ getTokenMarker() method
401: /**
402: * Returns the token marker for the given mode.
403: * You must override this method so that the mode loader can resolve
404: * delegate targets.
405: * @param mode The mode name
406: * @since jEdit 4.2pre1
407: */
408: protected abstract TokenMarker getTokenMarker(String mode);
409:
410: //}}}
411:
412: //}}}
413:
414: //{{{ Private members
415:
416: //{{{ Instance variables
417: private String modeName;
418: /** The token marker cannot be null. */
419: private final TokenMarker marker;
420: private KeywordMap keywords;
421: /** this stack can contains null elements. */
422: private Stack<TagDecl> stateStack;
423: private String propName;
424: private String propValue;
425: private Hashtable<String, String> props;
426: private Hashtable<String, String> modeProps;
427: private ParserRuleSet rules;
428: /**
429: * A list of modes to be reloaded at the end, loaded through DELEGATEs
430: * @see http://sourceforge.net/tracker/index.php?func=detail&aid=1742250&group_id=588&atid=100588
431: */
432: private Vector<Mode> reloadModes;
433:
434: //}}}
435:
436: //{{{ addKeyword() method
437: private void addKeyword(String k, byte id) {
438: if (k == null) {
439: error("empty-keyword", null);
440: return;
441: }
442:
443: if (keywords == null)
444: return;
445: keywords.add(k, id);
446: } //}}}
447:
448: //{{{ pushElement() method
449: private TagDecl pushElement(String name, Attributes attrs) {
450: if (name != null) {
451: TagDecl tag = new TagDecl(name, attrs);
452: stateStack.push(tag);
453: return tag;
454: } else {
455: stateStack.push(null);
456: return null;
457: }
458: } //}}}
459:
460: //{{{ peekElement() method
461: private TagDecl peekElement() {
462: return stateStack.peek();
463: } //}}}
464:
465: //{{{ popElement() method
466: private TagDecl popElement() {
467: return stateStack.pop();
468: } //}}}
469:
470: //{{{ findParent() method
471: /**
472: * Finds the first element whose tag matches 'tagName',
473: * searching backwards in the stack.
474: */
475: private TagDecl findParent(String tagName) {
476: for (int idx = stateStack.size() - 1; idx >= 0; idx--) {
477: TagDecl tag = stateStack.get(idx);
478: if (tag.tagName.equals(tagName))
479: return tag;
480: }
481: return null;
482: } //}}}
483:
484: //}}}
485:
486: /**
487: * Hold info about what tag was read and what attributes were
488: * set in the XML file, to be kept by the handler in a stack
489: * (similar to the way tag names were kept in a stack before).
490: */
491: private class TagDecl {
492:
493: public TagDecl(String tagName, Attributes attrs) {
494: this .tagName = tagName;
495:
496: String tmp;
497:
498: propName = attrs.getValue("NAME");
499: propValue = attrs.getValue("VALUE");
500:
501: tmp = attrs.getValue("TYPE");
502: if (tmp != null) {
503: lastTokenID = Token.stringToToken(tmp);
504: if (lastTokenID == -1)
505: error("token-invalid", tmp);
506: }
507:
508: lastMatchType = ParserRule.MATCH_TYPE_RULE;
509: // check for the deprecated "EXCLUDE_MATCH" and
510: // warn if found.
511: tmp = attrs.getValue("EXCLUDE_MATCH");
512: if (tmp != null) {
513: Log.log(Log.WARNING, this , modeName
514: + ": EXCLUDE_MATCH is deprecated");
515: if ("TRUE".equalsIgnoreCase(tmp)) {
516: lastMatchType = ParserRule.MATCH_TYPE_CONTEXT;
517: }
518: }
519:
520: // override with the newer MATCH_TYPE if present
521: tmp = attrs.getValue("MATCH_TYPE");
522: if (tmp != null) {
523: if ("CONTEXT".equals(tmp)) {
524: lastMatchType = ParserRule.MATCH_TYPE_CONTEXT;
525: } else if ("RULE".equals(tmp)) {
526: lastMatchType = ParserRule.MATCH_TYPE_RULE;
527: } else {
528: lastMatchType = Token.stringToToken(tmp);
529: if (lastMatchType == -1)
530: error("token-invalid", tmp);
531: }
532: }
533:
534: lastAtLineStart = "TRUE".equals(attrs
535: .getValue("AT_LINE_START"));
536: lastAtWhitespaceEnd = "TRUE".equals(attrs
537: .getValue("AT_WHITESPACE_END"));
538: lastAtWordStart = "TRUE".equals(attrs
539: .getValue("AT_WORD_START"));
540: lastNoLineBreak = "TRUE".equals(attrs
541: .getValue("NO_LINE_BREAK"));
542: lastNoWordBreak = "TRUE".equals(attrs
543: .getValue("NO_WORD_BREAK"));
544: lastIgnoreCase = (attrs.getValue("IGNORE_CASE") == null || "TRUE"
545: .equals(attrs.getValue("IGNORE_CASE")));
546: lastHighlightDigits = "TRUE".equals(attrs
547: .getValue("HIGHLIGHT_DIGITS"));
548: ;
549: lastDigitRE = attrs.getValue("DIGIT_RE");
550:
551: tmp = attrs.getValue("NO_WORD_SEP");
552: if (tmp != null)
553: lastNoWordSep = tmp;
554:
555: tmp = attrs.getValue("AT_CHAR");
556: if (tmp != null) {
557: try {
558: termChar = Integer.parseInt(tmp);
559: } catch (NumberFormatException e) {
560: error("termchar-invalid", tmp);
561: termChar = -1;
562: }
563: }
564:
565: lastEscape = attrs.getValue("ESCAPE");
566: lastSetName = attrs.getValue("SET");
567:
568: tmp = attrs.getValue("DELEGATE");
569: if (tmp != null) {
570: String delegateMode, delegateSetName;
571:
572: int index = tmp.indexOf("::");
573:
574: if (index != -1) {
575: delegateMode = tmp.substring(0, index);
576: delegateSetName = tmp.substring(index + 2);
577: } else {
578: delegateMode = modeName;
579: delegateSetName = tmp;
580: }
581:
582: TokenMarker delegateMarker = getTokenMarker(delegateMode);
583: if (delegateMarker == null)
584: error("delegate-invalid", tmp);
585: else {
586: lastDelegateSet = delegateMarker
587: .getRuleSet(delegateSetName);
588: if (delegateMarker == marker
589: && lastDelegateSet == null) {
590: // stupid hack to handle referencing
591: // a rule set that is defined later!
592: lastDelegateSet = new ParserRuleSet(
593: delegateMode, delegateSetName);
594: lastDelegateSet.setDefault(Token.INVALID);
595: marker.addRuleSet(lastDelegateSet);
596: } else if (lastDelegateSet == null)
597: error("delegate-invalid", tmp);
598: }
599: }
600:
601: tmp = attrs.getValue("DEFAULT");
602: if (tmp != null) {
603: lastDefaultID = Token.stringToToken(tmp);
604: if (lastDefaultID == -1) {
605: error("token-invalid", tmp);
606: lastDefaultID = Token.NULL;
607: }
608: }
609:
610: lastHashChar = attrs.getValue("HASH_CHAR");
611: lastHashChars = attrs.getValue("HASH_CHARS");
612: if ((null != lastHashChar) && (null != lastHashChars)) {
613: error("hash-char-and-hash-chars-mutually-exclusive",
614: null);
615: lastHashChars = null;
616: }
617: }
618:
619: public void setText(char[] c, int off, int len) {
620: if (tagName.equals("EOL_SPAN")
621: || tagName.equals("EOL_SPAN_REGEXP")
622: || tagName.equals("MARK_PREVIOUS")
623: || tagName.equals("MARK_FOLLOWING")
624: || tagName.equals("SEQ")
625: || tagName.equals("SEQ_REGEXP")
626: || tagName.equals("BEGIN")) {
627: TagDecl target = this ;
628: if (tagName.equals("BEGIN"))
629: target = stateStack.get(stateStack.size() - 2);
630:
631: if (target.lastStart == null) {
632: target.lastStart = new StringBuffer();
633: target.lastStart.append(c, off, len);
634: target.lastStartPosMatch = ((target.lastAtLineStart ? ParserRule.AT_LINE_START
635: : 0)
636: | (target.lastAtWhitespaceEnd ? ParserRule.AT_WHITESPACE_END
637: : 0) | (target.lastAtWordStart ? ParserRule.AT_WORD_START
638: : 0));
639: target.lastAtLineStart = false;
640: target.lastAtWordStart = false;
641: target.lastAtWhitespaceEnd = false;
642: } else {
643: target.lastStart.append(c, off, len);
644: }
645: } else if (tagName.equals("END")) {
646: TagDecl target = stateStack.get(stateStack.size() - 2);
647: if (target.lastEnd == null) {
648: target.lastEnd = new StringBuffer();
649: target.lastEnd.append(c, off, len);
650: target.lastEndPosMatch = ((target.lastAtLineStart ? ParserRule.AT_LINE_START
651: : 0)
652: | (target.lastAtWhitespaceEnd ? ParserRule.AT_WHITESPACE_END
653: : 0) | (target.lastAtWordStart ? ParserRule.AT_WORD_START
654: : 0));
655: target.lastAtLineStart = false;
656: target.lastAtWordStart = false;
657: target.lastAtWhitespaceEnd = false;
658: } else {
659: target.lastEnd.append(c, off, len);
660: }
661: } else {
662: if (lastKeyword == null)
663: lastKeyword = new StringBuffer();
664: lastKeyword.append(c, off, len);
665: }
666: }
667:
668: public String tagName;
669: public StringBuffer lastStart;
670: public StringBuffer lastEnd;
671: public StringBuffer lastKeyword;
672: public String lastSetName;
673: public String lastEscape;
674: public ParserRuleSet lastDelegateSet;
675: public String lastNoWordSep = "_";
676: public ParserRuleSet rules;
677: public byte lastDefaultID = Token.NULL;
678: public byte lastTokenID;
679: public byte lastMatchType;
680: public int termChar = -1;
681: public boolean lastNoLineBreak;
682: public boolean lastNoWordBreak;
683: public boolean lastIgnoreCase = true;
684: public boolean lastHighlightDigits;
685: public boolean lastAtLineStart;
686: public boolean lastAtWhitespaceEnd;
687: public boolean lastAtWordStart;
688: public int lastStartPosMatch;
689: public int lastEndPosMatch;
690: public String lastDigitRE;
691: public String lastHashChar;
692: public String lastHashChars;
693: }
694: }
|