diff options
Diffstat (limited to 'lexers')
| -rw-r--r-- | lexers/LexCoffeeScript.cxx | 572 | 
1 files changed, 572 insertions, 0 deletions
diff --git a/lexers/LexCoffeeScript.cxx b/lexers/LexCoffeeScript.cxx new file mode 100644 index 000000000..590cef5e1 --- /dev/null +++ b/lexers/LexCoffeeScript.cxx @@ -0,0 +1,572 @@ +// Scintilla source code edit control +/** @file LexCoffeeScript.cxx + ** Lexer for CoffeeScript. + **/ +// Copyright 1998-2011 by Neil Hodgson <neilh@scintilla.org> +// Based on the Scintilla C++ Lexer +// Written by Eric Promislow <ericp@activestate.com> in 2011 for the Komodo IDE +// The License.txt file describes the conditions under which this software may be distributed. + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> +#include <assert.h> +#include <ctype.h> + +#include "Platform.h" +#include "ILexer.h" +#include "Scintilla.h" +#include "SciLexer.h" + +#include "WordList.h" +#include "LexAccessor.h" +#include "Accessor.h" +#include "StyleContext.h" +#include "CharacterSet.h" +#include "LexerModule.h" + +#ifdef SCI_NAMESPACE +using namespace Scintilla; +#endif + +static bool IsSpaceEquiv(int state) { +	return (state <= SCE_C_COMMENTDOC +	    // including SCE_C_DEFAULT, SCE_C_COMMENT, SCE_C_COMMENTLINE +	    || state == SCE_C_COMMENTLINEDOC +	    || state == SCE_C_COMMENTDOCKEYWORD +	    || state == SCE_C_COMMENTDOCKEYWORDERROR +	    || state == SCE_COFFEESCRIPT_COMMENTBLOCK +	    || state == SCE_COFFEESCRIPT_VERBOSE_REGEX +	    || state == SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT +	    || state == SCE_C_WORD +	    || state == SCE_C_REGEX); +} + +// Preconditions: sc.currentPos points to a character after '+' or '-'. +// The test for pos reaching 0 should be redundant, +// and is in only for safety measures. +// Limitation: this code will give the incorrect answer for code like +// a = b+++/ptn/... +// Putting a space between the '++' post-inc operator and the '+' binary op +// fixes this, and is highly recommended for readability anyway. +static bool FollowsPostfixOperator(StyleContext &sc, Accessor &styler) { +	int pos = (int) sc.currentPos; +	while (--pos > 0) { +		char ch = styler[pos]; +		if (ch == '+' || ch == '-') { +			return styler[pos - 1] == ch; +		} +	} +	return false; +} + +static bool followsReturnKeyword(StyleContext &sc, Accessor &styler) { +    // Don't look at styles, so no need to flush. +	int pos = (int) sc.currentPos; +	int currentLine = styler.GetLine(pos); +	int lineStartPos = styler.LineStart(currentLine); +	char ch; +	while (--pos > lineStartPos) { +		ch = styler.SafeGetCharAt(pos); +		if (ch != ' ' && ch != '\t') { +			break; +		} +	} +	const char *retBack = "nruter"; +	const char *s = retBack; +	while (*s +	       && pos >= lineStartPos +	       && styler.SafeGetCharAt(pos) == *s) { +		s++; +		pos--; +	} +	return !*s; +} + +static void ColouriseCoffeeScriptDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[], +                            Accessor &styler) { + +	WordList &keywords = *keywordlists[0]; +	WordList &keywords2 = *keywordlists[1]; +	WordList &keywords3 = *keywordlists[2]; +	WordList &keywords4 = *keywordlists[3]; + +	// property styling.within.preprocessor +	//	For C++ code, determines whether all preprocessor code is styled in the preprocessor style (0, the default) +	//	or only from the initial # to the end of the command word(1). +	bool stylingWithinPreprocessor = styler.GetPropertyInt("styling.within.preprocessor") != 0; + +	CharacterSet setOKBeforeRE(CharacterSet::setNone, "([{=,:;!%^&*|?~+-"); +	CharacterSet setCouldBePostOp(CharacterSet::setNone, "+-"); + +	CharacterSet setDoxygen(CharacterSet::setAlpha, "$@\\&<>#{}[]"); + +	CharacterSet setWordStart(CharacterSet::setAlpha, "_", 0x80, true); +	CharacterSet setWord(CharacterSet::setAlphaNum, "._", 0x80, true); + +	// property lexer.cpp.allow.dollars +	//	Set to 0 to disallow the '$' character in identifiers with the cpp lexer. +	if (styler.GetPropertyInt("lexer.cpp.allow.dollars", 1) != 0) { +		setWordStart.Add('$'); +		setWord.Add('$'); +	} + +	int chPrevNonWhite = ' '; +	int visibleChars = 0; +	bool lastWordWasUUID = false; +	int styleBeforeDCKeyword = SCE_C_DEFAULT; +	bool continuationLine = false; +	bool isIncludePreprocessor = false; + +	if (initStyle == SCE_C_PREPROCESSOR) { +		// Set continuationLine if last character of previous line is '\' +		int lineCurrent = styler.GetLine(startPos); +		if (lineCurrent > 0) { +			int chBack = styler.SafeGetCharAt(startPos-1, 0); +			int chBack2 = styler.SafeGetCharAt(startPos-2, 0); +			int lineEndChar = '!'; +			if (chBack2 == '\r' && chBack == '\n') { +				lineEndChar = styler.SafeGetCharAt(startPos-3, 0); +			} else if (chBack == '\n' || chBack == '\r') { +				lineEndChar = chBack2; +			} +			continuationLine = lineEndChar == '\\'; +		} +	} + +	// look back to set chPrevNonWhite properly for better regex colouring +	int endPos = startPos + length; +	if (startPos > 0) { +		unsigned int back = startPos; +		styler.Flush(); +		while (back > 0 && IsSpaceEquiv(styler.StyleAt(--back))) +			; +		if (styler.StyleAt(back) == SCE_C_OPERATOR) { +			chPrevNonWhite = styler.SafeGetCharAt(back); +		} +		if (startPos != back) { +			initStyle = styler.StyleAt(back); +		} +		startPos = back; +	} + +	StyleContext sc(startPos, endPos - startPos, initStyle, styler); + +	for (; sc.More(); sc.Forward()) { + +		if (sc.atLineStart) { +			// Reset states to begining of colourise so no surprises +			// if different sets of lines lexed. +			visibleChars = 0; +			lastWordWasUUID = false; +			isIncludePreprocessor = false; +		} + +		// Handle line continuation generically. +		if (sc.ch == '\\') { +			if (sc.chNext == '\n' || sc.chNext == '\r') { +				sc.Forward(); +				if (sc.ch == '\r' && sc.chNext == '\n') { +					sc.Forward(); +				} +				continuationLine = true; +				continue; +			} +		} + +		// Determine if the current state should terminate. +		switch (sc.state) { +			case SCE_C_OPERATOR: +				sc.SetState(SCE_C_DEFAULT); +				break; +			case SCE_C_NUMBER: +				// We accept almost anything because of hex. and number suffixes +				if (!setWord.Contains(sc.ch)) { +					sc.SetState(SCE_C_DEFAULT); +				} +				break; +			case SCE_C_IDENTIFIER: +				if (!setWord.Contains(sc.ch) || (sc.ch == '.') || (sc.ch == '$')) { +					char s[1000]; +					sc.GetCurrent(s, sizeof(s)); +					if (keywords.InList(s)) { +						lastWordWasUUID = strcmp(s, "uuid") == 0; +						sc.ChangeState(SCE_C_WORD); +					} else if (keywords2.InList(s)) { +						sc.ChangeState(SCE_C_WORD2); +					} else if (keywords4.InList(s)) { +						sc.ChangeState(SCE_C_GLOBALCLASS); +					} +					sc.SetState(SCE_C_DEFAULT); +				} +				break; +			case SCE_C_PREPROCESSOR: +				if (sc.atLineStart && !continuationLine) { +					sc.SetState(SCE_C_DEFAULT); +				} else if (stylingWithinPreprocessor) { +					if (IsASpace(sc.ch)) { +						sc.SetState(SCE_C_DEFAULT); +					} +				} else { +					if (sc.Match('/', '*') || sc.Match('/', '/')) { +						sc.SetState(SCE_C_DEFAULT); +					} +				} +				break; +			case SCE_C_COMMENT: +				if (sc.Match('*', '/')) { +					sc.Forward(); +					sc.ForwardSetState(SCE_C_DEFAULT); +				} +				break; +			case SCE_C_COMMENTDOC: +				if (sc.Match('*', '/')) { +					sc.Forward(); +					sc.ForwardSetState(SCE_C_DEFAULT); +				} else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support +					// Verify that we have the conditions to mark a comment-doc-keyword +					if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) { +						styleBeforeDCKeyword = SCE_C_COMMENTDOC; +						sc.SetState(SCE_C_COMMENTDOCKEYWORD); +					} +				} +				break; +			case SCE_C_COMMENTLINE: +				if (sc.atLineStart) { +					sc.SetState(SCE_C_DEFAULT); +				} +				break; +			case SCE_C_COMMENTLINEDOC: +				if (sc.atLineStart) { +					sc.SetState(SCE_C_DEFAULT); +				} else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support +					// Verify that we have the conditions to mark a comment-doc-keyword +					if ((IsASpace(sc.chPrev) || sc.chPrev == '/' || sc.chPrev == '!') && (!IsASpace(sc.chNext))) { +						styleBeforeDCKeyword = SCE_C_COMMENTLINEDOC; +						sc.SetState(SCE_C_COMMENTDOCKEYWORD); +					} +				} +				break; +			case SCE_C_COMMENTDOCKEYWORD: +				if ((styleBeforeDCKeyword == SCE_C_COMMENTDOC) && sc.Match('*', '/')) { +					sc.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR); +					sc.Forward(); +					sc.ForwardSetState(SCE_C_DEFAULT); +				} else if (!setDoxygen.Contains(sc.ch)) { +					char s[100]; +					sc.GetCurrent(s, sizeof(s)); +					if (!IsASpace(sc.ch) || !keywords3.InList(s + 1)) { +						sc.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR); +					} +					sc.SetState(styleBeforeDCKeyword); +				} +				break; +			case SCE_C_STRING: +				if (isIncludePreprocessor) { +					if (sc.ch == '>') { +						sc.ForwardSetState(SCE_C_DEFAULT); +						isIncludePreprocessor = false; +					} +				} else if (sc.ch == '\\') { +					if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') { +						sc.Forward(); +					} +				} else if (sc.ch == '\"') { +					sc.ForwardSetState(SCE_C_DEFAULT); +				} +				break; +			case SCE_C_CHARACTER: +				if (sc.ch == '\\') { +					if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') { +						sc.Forward(); +					} +				} else if (sc.ch == '\'') { +					sc.ForwardSetState(SCE_C_DEFAULT); +				} +				break; +			case SCE_C_REGEX: +				if (sc.atLineStart) { +					sc.SetState(SCE_C_DEFAULT); +				} else if (sc.ch == '/') { +					sc.Forward(); +					while ((sc.ch < 0x80) && islower(sc.ch)) +						sc.Forward();    // gobble regex flags +					sc.SetState(SCE_C_DEFAULT); +				} else if (sc.ch == '\\') { +					// Gobble up the quoted character +					if (sc.chNext == '\\' || sc.chNext == '/') { +						sc.Forward(); +					} +				} +				break; +			case SCE_C_STRINGEOL: +				if (sc.atLineStart) { +					sc.SetState(SCE_C_DEFAULT); +				} +				break; +			case SCE_C_VERBATIM: +				if (sc.ch == '\"') { +					if (sc.chNext == '\"') { +						sc.Forward(); +					} else { +						sc.ForwardSetState(SCE_C_DEFAULT); +					} +				} +				break; +			case SCE_C_UUID: +				if (sc.ch == '\r' || sc.ch == '\n' || sc.ch == ')') { +					sc.SetState(SCE_C_DEFAULT); +				} +				break; +			case SCE_COFFEESCRIPT_COMMENTBLOCK: +				if (sc.Match("###")) { +					sc.ChangeState(SCE_C_COMMENT); +					sc.Forward(); +					sc.Forward(); +					sc.ForwardSetState(SCE_C_DEFAULT); +				} else if (sc.ch == '\\') { +					sc.Forward(); +				} +				break; +			case SCE_COFFEESCRIPT_VERBOSE_REGEX: +				if (sc.Match("///")) { +					sc.Forward(); +					sc.Forward(); +					sc.ChangeState(SCE_C_REGEX); +					sc.ForwardSetState(SCE_C_DEFAULT); +				} else if (sc.Match('#')) { +					sc.ChangeState(SCE_C_REGEX); +					sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT); +				} else if (sc.ch == '\\') { +					sc.Forward(); +				} +				break; +			case SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT: +				if (sc.atLineStart) { +					sc.ChangeState(SCE_C_COMMENT); +					sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX); +				} +				break; +		} + +		// Determine if a new state should be entered. +		if (sc.state == SCE_C_DEFAULT) { +			if (sc.Match('@', '\"')) { +				sc.SetState(SCE_C_VERBATIM); +				sc.Forward(); +			} else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) { +				if (lastWordWasUUID) { +					sc.SetState(SCE_C_UUID); +					lastWordWasUUID = false; +				} else { +					sc.SetState(SCE_C_NUMBER); +				} +			} else if (setWordStart.Contains(sc.ch) || (sc.ch == '@') || (sc.ch == '$')) { +				if (lastWordWasUUID) { +					sc.SetState(SCE_C_UUID); +					lastWordWasUUID = false; +				} else { +					sc.SetState(SCE_C_IDENTIFIER); +				} +			} else if (sc.Match('/', '*')) { +				if (sc.Match("/**") || sc.Match("/*!")) {	// Support of Qt/Doxygen doc. style +					sc.SetState(SCE_C_COMMENTDOC); +				} else { +					sc.SetState(SCE_C_COMMENT); +				} +				sc.Forward();	// Eat the * so it isn't used for the end of the comment +			} else if (sc.Match("///")) { +				sc.SetState(SCE_COFFEESCRIPT_VERBOSE_REGEX); +			} else if (sc.ch == '/' +				   && (setOKBeforeRE.Contains(chPrevNonWhite) +				       || followsReturnKeyword(sc, styler)) +				   && (!setCouldBePostOp.Contains(chPrevNonWhite) +				       || !FollowsPostfixOperator(sc, styler))) { +				sc.SetState(SCE_C_REGEX);	// JavaScript's RegEx +			} else if (sc.ch == '\"') { +				sc.SetState(SCE_C_STRING); +				isIncludePreprocessor = false;	// ensure that '>' won't end the string +			} else if (isIncludePreprocessor && sc.ch == '<') { +				sc.SetState(SCE_C_STRING); +			} else if (sc.ch == '\'') { +				sc.SetState(SCE_C_CHARACTER); +			} else if (sc.ch == '#') { +                if (sc.Match("###")) { +                    sc.SetState(SCE_COFFEESCRIPT_COMMENTBLOCK); +                } else { +                    sc.SetState(SCE_C_COMMENTLINE); +                } +			} else if (isoperator(static_cast<char>(sc.ch))) { +				sc.SetState(SCE_C_OPERATOR); +			} +		} + +		if (!IsASpace(sc.ch) && !IsSpaceEquiv(sc.state)) { +			chPrevNonWhite = sc.ch; +			visibleChars++; +		} +		continuationLine = false; +	} +    // Change temporary coffeescript states into standard C ones. +    switch (sc.state) { +        case SCE_COFFEESCRIPT_COMMENTBLOCK: +            sc.ChangeState(SCE_C_COMMENT); +            break; +        case SCE_COFFEESCRIPT_VERBOSE_REGEX: +            sc.ChangeState(SCE_C_REGEX); +            break; +        case SCE_COFFEESCRIPT_VERBOSE_REGEX_COMMENT: +            sc.ChangeState(SCE_C_COMMENTLINE); +            break; +    } +	sc.Complete(); +} + +static bool IsCommentLine(int line, Accessor &styler) { +	int pos = styler.LineStart(line); +	int eol_pos = styler.LineStart(line + 1) - 1; +	for (int i = pos; i < eol_pos; i++) { +		char ch = styler[i]; +		if (ch == '#') +			return true; +        else if (ch == '/' +                 && i < eol_pos - 1 +                 && styler[i + 1] == '*') +			return true; +		else if (ch != ' ' && ch != '\t') +			return false; +	} +	return false; +} + +static void FoldCoffeeScriptDoc(unsigned int startPos, int length, int, +				WordList *[], Accessor &styler) { +	// A simplified version of FoldPyDoc +	const int maxPos = startPos + length; +	const int maxLines = styler.GetLine(maxPos - 1);             // Requested last line +	const int docLines = styler.GetLine(styler.Length() - 1);  // Available last line + +	// property fold.coffeescript.comment +	const bool foldComment = styler.GetPropertyInt("fold.coffeescript.comment") != 0; + +	const bool foldCompact = styler.GetPropertyInt("fold.compact") != 0; + +	// Backtrack to previous non-blank line so we can determine indent level +	// for any white space lines +	// and so we can fix any preceding fold level (which is why we go back +	// at least one line in all cases) +	int spaceFlags = 0; +	int lineCurrent = styler.GetLine(startPos); +	int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL); +	while (lineCurrent > 0) { +		lineCurrent--; +		indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL); +		if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG) +		    && !IsCommentLine(lineCurrent, styler)) +			break; +	} +	int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK; + +	// Set up initial loop state +	startPos = styler.LineStart(lineCurrent); +	int prevComment = 0; +	if (lineCurrent >= 1) +		prevComment = foldComment && IsCommentLine(lineCurrent - 1, styler); + +	// Process all characters to end of requested range +	// or comment that hangs over the end of the range.  Cap processing in all cases +	// to end of document (in case of comment at end). +	while ((lineCurrent <= docLines) && ((lineCurrent <= maxLines) || prevComment)) { + +		// Gather info +		int lev = indentCurrent; +		int lineNext = lineCurrent + 1; +		int indentNext = indentCurrent; +		if (lineNext <= docLines) { +			// Information about next line is only available if not at end of document +			indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL); +		} +		const int comment = foldComment && IsCommentLine(lineCurrent, styler); +		const int comment_start = (comment && !prevComment && (lineNext <= docLines) && +		                           IsCommentLine(lineNext, styler) && (lev > SC_FOLDLEVELBASE)); +		const int comment_continue = (comment && prevComment); +		if (!comment) +			indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK; +		if (indentNext & SC_FOLDLEVELWHITEFLAG) +			indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel; + +		if (comment_start) { +			// Place fold point at start of a block of comments +			lev |= SC_FOLDLEVELHEADERFLAG; +		} else if (comment_continue) { +			// Add level to rest of lines in the block +			lev = lev + 1; +		} + +		// Skip past any blank lines for next indent level info; we skip also +		// comments (all comments, not just those starting in column 0) +		// which effectively folds them into surrounding code rather +		// than screwing up folding. + +		while ((lineNext < docLines) && +		        ((indentNext & SC_FOLDLEVELWHITEFLAG) || +		         (lineNext <= docLines && IsCommentLine(lineNext, styler)))) { + +			lineNext++; +			indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL); +		} + +		const int levelAfterComments = indentNext & SC_FOLDLEVELNUMBERMASK; +		const int levelBeforeComments = Platform::Maximum(indentCurrentLevel,levelAfterComments); + +		// Now set all the indent levels on the lines we skipped +		// Do this from end to start.  Once we encounter one line +		// which is indented more than the line after the end of +		// the comment-block, use the level of the block before + +		int skipLine = lineNext; +		int skipLevel = levelAfterComments; + +		while (--skipLine > lineCurrent) { +			int skipLineIndent = styler.IndentAmount(skipLine, &spaceFlags, NULL); + +			if (foldCompact) { +				if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments) +					skipLevel = levelBeforeComments; + +				int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG; + +				styler.SetLevel(skipLine, skipLevel | whiteFlag); +			} else { +				if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments && +					!(skipLineIndent & SC_FOLDLEVELWHITEFLAG) && +					!IsCommentLine(skipLine, styler)) +					skipLevel = levelBeforeComments; + +				styler.SetLevel(skipLine, skipLevel); +			} +		} + +		// Set fold header on non-comment line +		if (!comment && !(indentCurrent & SC_FOLDLEVELWHITEFLAG)) { +			if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK)) +				lev |= SC_FOLDLEVELHEADERFLAG; +		} + +		// Keep track of block comment state of previous line +		prevComment = comment_start || comment_continue; + +		// Set fold level for this line and move to next line +		styler.SetLevel(lineCurrent, lev); +		indentCurrent = indentNext; +		lineCurrent = lineNext; +	} +} + +static const char *const csWordLists[] = { +            "Keywords", +            0, +}; + +LexerModule lmCoffeeScript(SCLEX_COFFEESCRIPT, ColouriseCoffeeScriptDoc, "coffeescript", FoldCoffeeScriptDoc, csWordLists);  | 
