diff options
-rw-r--r-- | lexers/LexCPP.cxx | 758 |
1 files changed, 646 insertions, 112 deletions
diff --git a/lexers/LexCPP.cxx b/lexers/LexCPP.cxx index 8a9395e17..73ba70a42 100644 --- a/lexers/LexCPP.cxx +++ b/lexers/LexCPP.cxx @@ -10,16 +10,33 @@ #include <ctype.h> #include <stdio.h> #include <stdarg.h> +#include <assert.h> -#include "Platform.h" +#ifdef _MSC_VER +#pragma warning(disable: 4786) +#endif +#ifdef __BORLANDC__ +// Borland C++ displays warnings in vector header without this +#pragma option -w-ccc -w-rch +#endif -#include "PropSet.h" -#include "Accessor.h" -#include "StyleContext.h" -#include "KeyWords.h" +#include <string> +#include <vector> +#include <map> +#include <algorithm> + +#include "ILexer.h" #include "Scintilla.h" #include "SciLexer.h" + +#include "PropSetSimple.h" +#include "WordList.h" +#include "LexAccessor.h" +#include "Accessor.h" +#include "StyleContext.h" #include "CharacterSet.h" +#include "LexerModule.h" +#include "OptionSet.h" #ifdef SCI_NAMESPACE using namespace Scintilla; @@ -39,7 +56,7 @@ static bool IsSpaceEquiv(int state) { // 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) { +static bool FollowsPostfixOperator(StyleContext &sc, LexAccessor &styler) { int pos = (int) sc.currentPos; while (--pos > 0) { char ch = styler[pos]; @@ -50,18 +67,321 @@ static bool FollowsPostfixOperator(StyleContext &sc, Accessor &styler) { return false; } -static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[], - Accessor &styler, bool caseSensitive) { +static std::string GetRestOfLine(LexAccessor &styler, int start, bool allowSpace) { + std::string restOfLine; + int i =0; + char ch = styler.SafeGetCharAt(start + i, '\n'); + while ((ch != '\r') && (ch != '\n')) { + if (allowSpace || (ch != ' ')) + restOfLine += ch; + i++; + ch = styler.SafeGetCharAt(start + i, '\n'); + } + return restOfLine; +} + +static bool IsStreamCommentStyle(int style) { + return style == SCE_C_COMMENT || + style == SCE_C_COMMENTDOC || + style == SCE_C_COMMENTDOCKEYWORD || + style == SCE_C_COMMENTDOCKEYWORDERROR; +} + +static std::vector<std::string> Tokenize(const std::string &s) { + // Break into space separated tokens + std::string word; + std::vector<std::string> tokens; + for (const char *cp = s.c_str(); *cp; cp++) { + if ((*cp == ' ') || (*cp == '\t')) { + if (!word.empty()) { + tokens.push_back(word); + word = ""; + } + } else { + word += *cp; + } + } + if (!word.empty()) { + tokens.push_back(word); + } + return tokens; +} + +struct PPDefinition { + int line; + std::string key; + std::string value; + PPDefinition(int line_, const std::string &key_, const std::string &value_) : + line(line_), key(key_), value(value_) { + } +}; + +class LinePPState { + int state; + int ifTaken; + int level; + bool ValidLevel() const { + return level >= 0 && level < 32; + } + int maskLevel() const { + return 1 << level; + } +public: + LinePPState() : state(0), ifTaken(0), level(-1) { + } + bool IsInactive() const { + return state != 0; + } + bool CurrentIfTaken() { + return (ifTaken & maskLevel()) != 0; + } + void StartSection(bool on) { + level++; + if (ValidLevel()) { + if (on) { + state &= ~maskLevel(); + ifTaken |= maskLevel(); + } else { + state |= maskLevel(); + ifTaken &= ~maskLevel(); + } + } + } + void EndSection() { + if (ValidLevel()) { + state &= ~maskLevel(); + ifTaken &= ~maskLevel(); + } + level--; + } + void InvertCurrentLevel() { + if (ValidLevel()) { + state ^= maskLevel(); + ifTaken |= maskLevel(); + } + } +}; + +// Hold the preprocessor state for each line seen. +// Currently one entry per line but could become sparse with just one entry per preprocessor line. +class PPStates { + std::vector<LinePPState> vlls; +public: + LinePPState ForLine(int line) { + if ((line > 0) && (vlls.size() > static_cast<size_t>(line))) { + return vlls[line]; + } else { + return LinePPState(); + } + } + void Add(int line, LinePPState lls) { + vlls.resize(line+1); + vlls[line] = lls; + } +}; + +// An individual named option for use in an OptionSet + +// Options used for LexerCPP +struct OptionsCPP { + bool stylingWithinPreprocessor; + bool identifiersAllowDollars; + bool trackPreprocessor; + bool updatePreprocessor; + bool foldComment; + bool foldPreprocessor; + bool foldCompact; + bool foldAtElse; + int OO_i; + std::string OO_s; + OptionsCPP() { + stylingWithinPreprocessor = false; + identifiersAllowDollars = true; + trackPreprocessor = true; + updatePreprocessor = true; + foldComment = false; + foldPreprocessor = false; + foldCompact = false; + foldAtElse = false; + } +}; + +static const char *const cppWordLists[] = { + "Primary keywords and identifiers", + "Secondary keywords and identifiers", + "Documentation comment keywords", + "Global classes and typedefs", + "Preprocessor definitions", + 0, +}; + +struct OptionSetCPP : public OptionSet<OptionsCPP> { + OptionSetCPP() { + DefineProperty("styling.within.preprocessor", &OptionsCPP::stylingWithinPreprocessor, + "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)."); + + DefineProperty("lexer.cpp.allow.dollars", &OptionsCPP::identifiersAllowDollars, + "Set to 0 to disallow the '$' character in identifiers with the cpp lexer."); + + DefineProperty("lexer.cpp.track.preprocessor", &OptionsCPP::trackPreprocessor, + "Set to 1 to interpret #if/#else/#endif to grey out code that is not active."); + + DefineProperty("lexer.cpp.update.preprocessor", &OptionsCPP::updatePreprocessor, + "Set to 1 to update preprocessor definitions when #define found."); + + DefineProperty("fold.comment", &OptionsCPP::foldComment, + "This option enables folding multi-line comments and explicit fold points when using the C++ lexer. " + "Explicit fold points allows adding extra folding by placing a //{ comment at the start and a //} " + "at the end of a section that should fold."); + + DefineProperty("fold.preprocessor", &OptionsCPP::foldPreprocessor, + "This option enables folding preprocessor directives when using the C++ lexer. " + "Includes C#'s explicit #region and #endregion folding directives."); + + DefineProperty("fold.compact", &OptionsCPP::foldCompact); + + DefineProperty("fold.at.else", &OptionsCPP::foldAtElse, + "This option enables C++ folding on a \"} else {\" line of an if statement."); + + DefineWordListSets(cppWordLists); + } +}; + +class LexerCPP : public ILexer { + bool caseSensitive; + CharacterSet setWord; + CharacterSet setNegationOp; + CharacterSet setArithmethicOp; + CharacterSet setRelOp; + CharacterSet setLogicalOp; + PPStates vlls; + std::vector<PPDefinition> ppDefineHistory; + PropSetSimple props; + WordList keywords; + WordList keywords2; + WordList keywords3; + WordList keywords4; + WordList ppDefinitions; + std::map<std::string, std::string> preprocessorDefinitionsStart; + OptionsCPP options; + OptionSetCPP osCPP; +public: + LexerCPP(bool caseSensitive_) : + caseSensitive(caseSensitive_), + setWord(CharacterSet::setAlphaNum, "._", 0x80, true), + setNegationOp(CharacterSet::setNone, "!"), + setArithmethicOp(CharacterSet::setNone, "+-/*%"), + setRelOp(CharacterSet::setNone, "=!<>"), + setLogicalOp(CharacterSet::setNone, "|&") { + } + ~LexerCPP() { + } + void SCI_METHOD Release() { + delete this; + } + int SCI_METHOD Version() const { + return lvOriginal; + } + const char * SCI_METHOD PropertyNames() { + return osCPP.PropertyNames(); + } + int SCI_METHOD PropertyType(const char *name) { + return osCPP.PropertyType(name); + } + const char * SCI_METHOD DescribeProperty(const char *name) { + return osCPP.DescribeProperty(name); + } + int SCI_METHOD PropertySet(const char *key, const char *val); + const char * SCI_METHOD DescribeWordListSets() { + return osCPP.DescribeWordListSets(); + } + int SCI_METHOD WordListSet(int n, const char *wl); + void SCI_METHOD Lex(unsigned int startPos, int length, int initStyle, IDocument *pAccess); + void SCI_METHOD Fold(unsigned int startPos, int length, int initStyle, IDocument *pAccess); + + void * SCI_METHOD PrivateCall(int, void *) { + return 0; + } + + static ILexer *LexerFactoryCPP() { + return new LexerCPP(true); + } + static ILexer *LexerFactoryCPPInsensitive() { + return new LexerCPP(false); + } + + void EvaluateTokens(std::vector<std::string> &tokens); + bool EvaluateExpression(const std::string &expr, const std::map<std::string, std::string> &preprocessorDefinitions); +}; + +int SCI_METHOD LexerCPP::PropertySet(const char *key, const char *val) { + if (osCPP.PropertySet(&options, key, val)) { + return 0; + } + return -1; +} + +int SCI_METHOD LexerCPP::WordListSet(int n, const char *wl) { + WordList *wordListN = 0; + switch (n) { + case 0: + wordListN = &keywords; + break; + case 1: + wordListN = &keywords2; + break; + case 2: + wordListN = &keywords3; + break; + case 3: + wordListN = &keywords4; + break; + case 4: + wordListN = &ppDefinitions; + break; + } + int firstModification = -1; + if (wordListN) { + WordList wlNew; + wlNew.Set(wl); + if (*wordListN != wlNew) { + wordListN->Set(wl); + firstModification = 0; + if (n == 4) { + // Rebuild preprocessorDefinitions + preprocessorDefinitionsStart.clear(); + for (int nDefinition = 0; nDefinition < ppDefinitions.len; nDefinition++) { + char *cpDefinition = ppDefinitions.words[nDefinition]; + char *cpEquals = strchr(cpDefinition, '='); + if (cpEquals) { + std::string name(cpDefinition, cpEquals - cpDefinition); + std::string val(cpEquals+1); + preprocessorDefinitionsStart[name] = val; + } else { + std::string name(cpDefinition); + std::string val("1"); + preprocessorDefinitionsStart[name] = val; + } + } + } + } + } + return firstModification; +} - WordList &keywords = *keywordlists[0]; - WordList &keywords2 = *keywordlists[1]; - WordList &keywords3 = *keywordlists[2]; - WordList &keywords4 = *keywordlists[3]; +// Functor used to truncate history +struct After { + int line; + After(int line_) : line(line_) {} + bool operator() (PPDefinition &p) const { + return p.line > line; + } +}; - // 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; +void SCI_METHOD LexerCPP::Lex(unsigned int startPos, int length, int initStyle, IDocument *pAccess) { + LexAccessor styler(pAccess); CharacterSet setOKBeforeRE(CharacterSet::setNone, "([{=,:;!%^&*|?~+-"); CharacterSet setCouldBePostOp(CharacterSet::setNone, "+-"); @@ -71,9 +391,7 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo 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) { + if (options.identifiersAllowDollars) { setWordStart.Add('$'); setWord.Add('$'); } @@ -85,9 +403,9 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo bool continuationLine = false; bool isIncludePreprocessor = false; + int lineCurrent = styler.GetLine(startPos); 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); @@ -111,7 +429,30 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo } } - StyleContext sc(startPos, length, initStyle, styler); + StyleContext sc(startPos, length, initStyle, styler, 0x7f); + LinePPState preproc = vlls.ForLine(lineCurrent); + + bool definitionsChanged = false; + + // Truncate ppDefineHistory before current line + + if (!options.updatePreprocessor) + ppDefineHistory.clear(); + + std::vector<PPDefinition>::iterator itInvalid = std::find_if(ppDefineHistory.begin(), ppDefineHistory.end(), After(lineCurrent-1)); + if (itInvalid != ppDefineHistory.end()) { + ppDefineHistory.erase(itInvalid, ppDefineHistory.end()); + definitionsChanged = true; + } + + std::map<std::string, std::string> preprocessorDefinitions = preprocessorDefinitionsStart; + for (std::vector<PPDefinition>::iterator itDef = ppDefineHistory.begin(); itDef != ppDefineHistory.end(); ++itDef) { + preprocessorDefinitions[itDef->key] = itDef->value; + } + + const int maskActivity = 0x3F; + + int activitySet = preproc.IsInactive() ? 0x40 : 0; for (; sc.More(); sc.Forward()) { @@ -126,6 +467,22 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo visibleChars = 0; lastWordWasUUID = false; isIncludePreprocessor = false; + if (preproc.IsInactive()) { + activitySet = 0x40; + sc.SetState(sc.state | activitySet); + } + if (activitySet) { + if (sc.ch == '#') { + if (sc.Match("#else") || sc.Match("#end") || sc.Match("#if")) { + //activitySet = 0; + } + } + } + } + + if (sc.atLineEnd) { + lineCurrent++; + vlls.Add(lineCurrent, preproc); } // Handle line continuation generically. @@ -141,14 +498,14 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo } // Determine if the current state should terminate. - switch (sc.state) { + switch (sc.state & maskActivity) { case SCE_C_OPERATOR: - sc.SetState(SCE_C_DEFAULT); + sc.SetState(SCE_C_DEFAULT|activitySet); 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); + sc.SetState(SCE_C_DEFAULT|activitySet); } break; case SCE_C_IDENTIFIER: @@ -161,59 +518,59 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo } if (keywords.InList(s)) { lastWordWasUUID = strcmp(s, "uuid") == 0; - sc.ChangeState(SCE_C_WORD); + sc.ChangeState(SCE_C_WORD|activitySet); } else if (keywords2.InList(s)) { - sc.ChangeState(SCE_C_WORD2); + sc.ChangeState(SCE_C_WORD2|activitySet); } else if (keywords4.InList(s)) { - sc.ChangeState(SCE_C_GLOBALCLASS); + sc.ChangeState(SCE_C_GLOBALCLASS|activitySet); } - sc.SetState(SCE_C_DEFAULT); + sc.SetState(SCE_C_DEFAULT|activitySet); } break; case SCE_C_PREPROCESSOR: if (sc.atLineStart && !continuationLine) { - sc.SetState(SCE_C_DEFAULT); - } else if (stylingWithinPreprocessor) { + sc.SetState(SCE_C_DEFAULT|activitySet); + } else if (options.stylingWithinPreprocessor) { if (IsASpace(sc.ch)) { - sc.SetState(SCE_C_DEFAULT); + sc.SetState(SCE_C_DEFAULT|activitySet); } } else { if (sc.Match('/', '*') || sc.Match('/', '/')) { - sc.SetState(SCE_C_DEFAULT); + sc.SetState(SCE_C_DEFAULT|activitySet); } } break; case SCE_C_COMMENT: if (sc.Match('*', '/')) { sc.Forward(); - sc.ForwardSetState(SCE_C_DEFAULT); + sc.ForwardSetState(SCE_C_DEFAULT|activitySet); } break; case SCE_C_COMMENTDOC: if (sc.Match('*', '/')) { sc.Forward(); - sc.ForwardSetState(SCE_C_DEFAULT); + sc.ForwardSetState(SCE_C_DEFAULT|activitySet); } 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); + sc.SetState(SCE_C_COMMENTDOCKEYWORD|activitySet); } } break; case SCE_C_COMMENTLINE: if (sc.atLineStart) { - sc.SetState(SCE_C_DEFAULT); + sc.SetState(SCE_C_DEFAULT|activitySet); } break; case SCE_C_COMMENTLINEDOC: if (sc.atLineStart) { - sc.SetState(SCE_C_DEFAULT); + sc.SetState(SCE_C_DEFAULT|activitySet); } 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); + sc.SetState(SCE_C_COMMENTDOCKEYWORD|activitySet); } } break; @@ -221,7 +578,7 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo if ((styleBeforeDCKeyword == SCE_C_COMMENTDOC) && sc.Match('*', '/')) { sc.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR); sc.Forward(); - sc.ForwardSetState(SCE_C_DEFAULT); + sc.ForwardSetState(SCE_C_DEFAULT|activitySet); } else if (!setDoxygen.Contains(sc.ch)) { char s[100]; if (caseSensitive) { @@ -230,17 +587,17 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo sc.GetCurrentLowered(s, sizeof(s)); } if (!IsASpace(sc.ch) || !keywords3.InList(s + 1)) { - sc.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR); + sc.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR|activitySet); } sc.SetState(styleBeforeDCKeyword); } break; case SCE_C_STRING: if (sc.atLineEnd) { - sc.ChangeState(SCE_C_STRINGEOL); + sc.ChangeState(SCE_C_STRINGEOL|activitySet); } else if (isIncludePreprocessor) { if (sc.ch == '>') { - sc.ForwardSetState(SCE_C_DEFAULT); + sc.ForwardSetState(SCE_C_DEFAULT|activitySet); isIncludePreprocessor = false; } } else if (sc.ch == '\\') { @@ -248,28 +605,28 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo sc.Forward(); } } else if (sc.ch == '\"') { - sc.ForwardSetState(SCE_C_DEFAULT); + sc.ForwardSetState(SCE_C_DEFAULT|activitySet); } break; case SCE_C_CHARACTER: if (sc.atLineEnd) { - sc.ChangeState(SCE_C_STRINGEOL); + sc.ChangeState(SCE_C_STRINGEOL|activitySet); } else if (sc.ch == '\\') { if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') { sc.Forward(); } } else if (sc.ch == '\'') { - sc.ForwardSetState(SCE_C_DEFAULT); + sc.ForwardSetState(SCE_C_DEFAULT|activitySet); } break; case SCE_C_REGEX: if (sc.atLineStart) { - sc.SetState(SCE_C_DEFAULT); + sc.SetState(SCE_C_DEFAULT|activitySet); } else if (sc.ch == '/') { sc.Forward(); while ((sc.ch < 0x80) && islower(sc.ch)) sc.Forward(); // gobble regex flags - sc.SetState(SCE_C_DEFAULT); + sc.SetState(SCE_C_DEFAULT|activitySet); } else if (sc.ch == '\\') { // Gobble up the quoted character if (sc.chNext == '\\' || sc.chNext == '/') { @@ -279,7 +636,7 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo break; case SCE_C_STRINGEOL: if (sc.atLineStart) { - sc.SetState(SCE_C_DEFAULT); + sc.SetState(SCE_C_DEFAULT|activitySet); } break; case SCE_C_VERBATIM: @@ -287,72 +644,138 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo if (sc.chNext == '\"') { sc.Forward(); } else { - sc.ForwardSetState(SCE_C_DEFAULT); + sc.ForwardSetState(SCE_C_DEFAULT|activitySet); } } break; case SCE_C_UUID: if (sc.ch == '\r' || sc.ch == '\n' || sc.ch == ')') { - sc.SetState(SCE_C_DEFAULT); + sc.SetState(SCE_C_DEFAULT|activitySet); } } // Determine if a new state should be entered. - if (sc.state == SCE_C_DEFAULT) { + if ((sc.state & maskActivity) == SCE_C_DEFAULT) { if (sc.Match('@', '\"')) { - sc.SetState(SCE_C_VERBATIM); + sc.SetState(SCE_C_VERBATIM|activitySet); sc.Forward(); } else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) { if (lastWordWasUUID) { - sc.SetState(SCE_C_UUID); + sc.SetState(SCE_C_UUID|activitySet); lastWordWasUUID = false; } else { - sc.SetState(SCE_C_NUMBER); + sc.SetState(SCE_C_NUMBER|activitySet); } } else if (setWordStart.Contains(sc.ch) || (sc.ch == '@')) { if (lastWordWasUUID) { - sc.SetState(SCE_C_UUID); + sc.SetState(SCE_C_UUID|activitySet); lastWordWasUUID = false; } else { - sc.SetState(SCE_C_IDENTIFIER); + sc.SetState(SCE_C_IDENTIFIER|activitySet); } } else if (sc.Match('/', '*')) { if (sc.Match("/**") || sc.Match("/*!")) { // Support of Qt/Doxygen doc. style - sc.SetState(SCE_C_COMMENTDOC); + sc.SetState(SCE_C_COMMENTDOC|activitySet); } else { - sc.SetState(SCE_C_COMMENT); + sc.SetState(SCE_C_COMMENT|activitySet); } sc.Forward(); // Eat the * so it isn't used for the end of the comment } else if (sc.Match('/', '/')) { if ((sc.Match("///") && !sc.Match("////")) || sc.Match("//!")) // Support of Qt/Doxygen doc. style - sc.SetState(SCE_C_COMMENTLINEDOC); + sc.SetState(SCE_C_COMMENTLINEDOC|activitySet); else - sc.SetState(SCE_C_COMMENTLINE); + sc.SetState(SCE_C_COMMENTLINE|activitySet); } else if (sc.ch == '/' && setOKBeforeRE.Contains(chPrevNonWhite) && (!setCouldBePostOp.Contains(chPrevNonWhite) || !FollowsPostfixOperator(sc, styler))) { - sc.SetState(SCE_C_REGEX); // JavaScript's RegEx + sc.SetState(SCE_C_REGEX|activitySet); // JavaScript's RegEx } else if (sc.ch == '\"') { - sc.SetState(SCE_C_STRING); + sc.SetState(SCE_C_STRING|activitySet); isIncludePreprocessor = false; // ensure that '>' won't end the string } else if (isIncludePreprocessor && sc.ch == '<') { - sc.SetState(SCE_C_STRING); + sc.SetState(SCE_C_STRING|activitySet); } else if (sc.ch == '\'') { - sc.SetState(SCE_C_CHARACTER); + sc.SetState(SCE_C_CHARACTER|activitySet); } else if (sc.ch == '#' && visibleChars == 0) { // Preprocessor commands are alone on their line - sc.SetState(SCE_C_PREPROCESSOR); + sc.SetState(SCE_C_PREPROCESSOR|activitySet); // Skip whitespace between # and preprocessor word do { sc.Forward(); } while ((sc.ch == ' ' || sc.ch == '\t') && sc.More()); if (sc.atLineEnd) { - sc.SetState(SCE_C_DEFAULT); + sc.SetState(SCE_C_DEFAULT|activitySet); } else if (sc.Match("include")) { isIncludePreprocessor = true; + } else { + if (options.trackPreprocessor) { + if (sc.Match("ifdef") || sc.Match("ifndef")) { + bool isIfDef = sc.Match("ifdef"); + int i = isIfDef ? 5 : 6; + std::string restOfLine = GetRestOfLine(styler, sc.currentPos + i + 1, false); + bool foundDef = preprocessorDefinitions.find(restOfLine) != preprocessorDefinitions.end(); + preproc.StartSection(isIfDef == foundDef); + } else if (sc.Match("if")) { + std::string restOfLine = GetRestOfLine(styler, sc.currentPos + 2, true); + bool ifGood = EvaluateExpression(restOfLine, preprocessorDefinitions); + preproc.StartSection(ifGood); + } else if (sc.Match("else")) { + if (!preproc.CurrentIfTaken()) { + preproc.InvertCurrentLevel(); + activitySet = preproc.IsInactive() ? 0x40 : 0; + if (!activitySet) + sc.ChangeState(SCE_C_PREPROCESSOR|activitySet); + } else if (!preproc.IsInactive()) { + preproc.InvertCurrentLevel(); + activitySet = preproc.IsInactive() ? 0x40 : 0; + if (!activitySet) + sc.ChangeState(SCE_C_PREPROCESSOR|activitySet); + } + } else if (sc.Match("elif")) { + // Ensure only one chosen out of #if .. #elif .. #elif .. #else .. #endif + if (!preproc.CurrentIfTaken()) { + // Similar to #if + std::string restOfLine = GetRestOfLine(styler, sc.currentPos + 2, true); + bool ifGood = EvaluateExpression(restOfLine, preprocessorDefinitions); + if (ifGood) { + preproc.InvertCurrentLevel(); + activitySet = preproc.IsInactive() ? 0x40 : 0; + if (!activitySet) + sc.ChangeState(SCE_C_PREPROCESSOR|activitySet); + } + } else if (!preproc.IsInactive()) { + preproc.InvertCurrentLevel(); + activitySet = preproc.IsInactive() ? 0x40 : 0; + if (!activitySet) + sc.ChangeState(SCE_C_PREPROCESSOR|activitySet); + } + } else if (sc.Match("endif")) { + preproc.EndSection(); + activitySet = preproc.IsInactive() ? 0x40 : 0; + sc.ChangeState(SCE_C_PREPROCESSOR|activitySet); + } else if (sc.Match("define")) { + if (options.updatePreprocessor && !preproc.IsInactive()) { + std::string restOfLine = GetRestOfLine(styler, sc.currentPos + 6, true); + if (restOfLine.find(")") == std::string::npos) { // Don't handle macros with arguments + std::vector<std::string> tokens = Tokenize(restOfLine); + std::string key; + std::string value("1"); + if (tokens.size() >= 1) { + key = tokens[0]; + if (tokens.size() >= 2) { + value = tokens[1]; + } + preprocessorDefinitions[key] = value; + ppDefineHistory.push_back(PPDefinition(lineCurrent, key, value)); + definitionsChanged = true; + } + } + } + } + } } } else if (isoperator(static_cast<char>(sc.ch))) { - sc.SetState(SCE_C_OPERATOR); + sc.SetState(SCE_C_OPERATOR|activitySet); } } @@ -362,38 +785,19 @@ static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, Wo } continuationLine = false; } + if (definitionsChanged) + styler.ChangeLexerState(startPos, startPos + length); sc.Complete(); -} - -static bool IsStreamCommentStyle(int style) { - return style == SCE_C_COMMENT || - style == SCE_C_COMMENTDOC || - style == SCE_C_COMMENTDOCKEYWORD || - style == SCE_C_COMMENTDOCKEYWORDERROR; + styler.Flush(); } // Store both the current line's fold level and the next lines in the // level store to make it easy to pick up with each increment // and to make it possible to fiddle the current level for "} else {". -static void FoldCppDoc(unsigned int startPos, int length, int initStyle, - WordList *[], Accessor &styler) { - - // property fold.comment - // This option enables folding multi-line comments and explicit fold points when using the C++ lexer. - // Explicit fold points allows adding extra folding by placing a //{ comment at the start and a //} - // at the end of a section that should fold. - bool foldComment = styler.GetPropertyInt("fold.comment") != 0; - - // property fold.preprocessor - // This option enables folding preprocessor directives when using the C++ lexer. - // Includes C#'s explicit #region and #endregion folding directives. - bool foldPreprocessor = styler.GetPropertyInt("fold.preprocessor") != 0; - bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0; +void SCI_METHOD LexerCPP::Fold(unsigned int startPos, int length, int initStyle, IDocument *pAccess) { - // property fold.at.else - // This option enables C++ folding on a "} else {" line of an if statement. - bool foldAtElse = styler.GetPropertyInt("fold.at.else", 0) != 0; + LexAccessor styler(pAccess); unsigned int endPos = startPos + length; int visibleChars = 0; @@ -413,7 +817,7 @@ static void FoldCppDoc(unsigned int startPos, int length, int initStyle, style = styleNext; styleNext = styler.StyleAt(i + 1); bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n'); - if (foldComment && IsStreamCommentStyle(style)) { + if (options.foldComment && IsStreamCommentStyle(style)) { if (!IsStreamCommentStyle(stylePrev) && (stylePrev != SCE_C_COMMENTLINEDOC)) { levelNext++; } else if (!IsStreamCommentStyle(styleNext) && (styleNext != SCE_C_COMMENTLINEDOC) && !atEOL) { @@ -421,7 +825,7 @@ static void FoldCppDoc(unsigned int startPos, int length, int initStyle, levelNext--; } } - if (foldComment && (style == SCE_C_COMMENTLINE)) { + if (options.foldComment && (style == SCE_C_COMMENTLINE)) { if ((ch == '/') && (chNext == '/')) { char chNext2 = styler.SafeGetCharAt(i + 2); if (chNext2 == '{') { @@ -431,7 +835,7 @@ static void FoldCppDoc(unsigned int startPos, int length, int initStyle, } } } - if (foldPreprocessor && (style == SCE_C_PREPROCESSOR)) { + if (options.foldPreprocessor && (style == SCE_C_PREPROCESSOR)) { if (ch == '#') { unsigned int j = i + 1; while ((j < endPos) && IsASpaceOrTab(styler.SafeGetCharAt(j))) { @@ -460,11 +864,11 @@ static void FoldCppDoc(unsigned int startPos, int length, int initStyle, visibleChars++; if (atEOL || (i == endPos-1)) { int levelUse = levelCurrent; - if (foldAtElse) { + if (options.foldAtElse) { levelUse = levelMinCurrent; } int lev = levelUse | levelNext << 16; - if (visibleChars == 0 && foldCompact) + if (visibleChars == 0 && options.foldCompact) lev |= SC_FOLDLEVELWHITEFLAG; if (levelUse < levelNext) lev |= SC_FOLDLEVELHEADERFLAG; @@ -483,24 +887,154 @@ static void FoldCppDoc(unsigned int startPos, int length, int initStyle, } } -static const char *const cppWordLists[] = { - "Primary keywords and identifiers", - "Secondary keywords and identifiers", - "Documentation comment keywords", - "Unused", - "Global classes and typedefs", - 0, -}; +void LexerCPP::EvaluateTokens(std::vector<std::string> &tokens) { + + // Evaluate defined() statements to either 0 or 1 + for (size_t i=0; (i+2)<tokens.size();) { + if ((tokens[i] == "defined") && (tokens[i+1] == "(")) { + const char *val = "0"; + if (tokens[i+2] == ")") { + // defined() + tokens.erase(tokens.begin() + i + 1, tokens.begin() + i + 3); + } else if (((i+2)<tokens.size()) && (tokens[i+3] == ")")) { + // defined(<int>) + tokens.erase(tokens.begin() + i + 1, tokens.begin() + i + 4); + val = "1"; + } + tokens[i] = val; + } else { + i++; + } + } + + // Find bracketed subexpressions and recurse on them + std::vector<std::string>::iterator itBracket = std::find(tokens.begin(), tokens.end(), "("); + std::vector<std::string>::iterator itEndBracket = std::find(tokens.begin(), tokens.end(), ")"); + while ((itBracket != tokens.end()) && (itEndBracket != tokens.end()) && (itEndBracket > itBracket)) { + std::vector<std::string> inBracket(itBracket + 1, itEndBracket); + EvaluateTokens(inBracket); -static void ColouriseCppDocSensitive(unsigned int startPos, int length, int initStyle, WordList *keywordlists[], - Accessor &styler) { - ColouriseCppDoc(startPos, length, initStyle, keywordlists, styler, true); + // The insertion is done before the removal because there were failures with the opposite approach + tokens.insert(itBracket, inBracket.begin(), inBracket.end()); + itBracket = std::find(tokens.begin(), tokens.end(), "("); + itEndBracket = std::find(tokens.begin(), tokens.end(), ")"); + tokens.erase(itBracket, itEndBracket + 1); + + itBracket = std::find(tokens.begin(), tokens.end(), "("); + itEndBracket = std::find(tokens.begin(), tokens.end(), ")"); + } + + // Evaluate logical negations + for (size_t j=0; (j+1)<tokens.size();) { + if (setNegationOp.Contains(tokens[j][0])) { + int isTrue = atoi(tokens[j+1].c_str()); + if (tokens[j] == "!") + isTrue = !isTrue; + std::vector<std::string>::iterator itInsert = + tokens.erase(tokens.begin() + j, tokens.begin() + j + 2); + tokens.insert(itInsert, isTrue ? "1" : "0"); + } else { + j++; + } + } + + // Evaluate expressions in precedence order + enum precedence { precArithmetic, precRelative, precLogical }; + for (int prec=precArithmetic; prec <= precLogical; prec++) { + // Looking at 3 tokens at a time so end at 2 before end + for (size_t k=0; (k+2)<tokens.size();) { + char chOp = tokens[k+1][0]; + if ( + ((prec==precArithmetic) && setArithmethicOp.Contains(chOp)) || + ((prec==precRelative) && setRelOp.Contains(chOp)) || + ((prec==precLogical) && setLogicalOp.Contains(chOp)) + ) { + int valA = atoi(tokens[k].c_str()); + int valB = atoi(tokens[k+2].c_str()); + int result = 0; + if (tokens[k+1] == "+") + result = valA + valB; + else if (tokens[k+1] == "-") + result = valA - valB; + else if (tokens[k+1] == "*") + result = valA * valB; + else if (tokens[k+1] == "/") + result = valA / valB; + else if (tokens[k+1] == "%") + result = valA % valB; + else if (tokens[k+1] == "<") + result = valA < valB; + else if (tokens[k+1] == "<=") + result = valA <= valB; + else if (tokens[k+1] == ">") + result = valA > valB; + else if (tokens[k+1] == ">=") + result = valA >= valB; + else if (tokens[k+1] == "==") + result = valA == valB; + else if (tokens[k+1] == "!=") + result = valA != valB; + else if (tokens[k+1] == "||") + result = valA || valB; + else if (tokens[k+1] == "&&") + result = valA && valB; + char sResult[30]; + sprintf(sResult, "%d", result); + std::vector<std::string>::iterator itInsert = + tokens.erase(tokens.begin() + k, tokens.begin() + k + 3); + tokens.insert(itInsert, sResult); + } else { + k++; + } + } + } } -static void ColouriseCppDocInsensitive(unsigned int startPos, int length, int initStyle, WordList *keywordlists[], - Accessor &styler) { - ColouriseCppDoc(startPos, length, initStyle, keywordlists, styler, false); +bool LexerCPP::EvaluateExpression(const std::string &expr, const std::map<std::string, std::string> &preprocessorDefinitions) { + // Break into tokens, replacing with definitions + std::string word; + std::vector<std::string> tokens; + const char *cp = expr.c_str(); + for (;;) { + if (setWord.Contains(*cp)) { + word += *cp; + } else { + std::map<std::string, std::string>::const_iterator it = preprocessorDefinitions.find(word); + if (it != preprocessorDefinitions.end()) { + tokens.push_back(it->second); + } else if (!word.empty() && ((word[0] >= '0' && word[0] <= '9') || (word == "defined"))) { + tokens.push_back(word); + } + word = ""; + if (!*cp) { + break; + } + if ((*cp != ' ') && (*cp != '\t')) { + std::string op(cp, 1); + if (setRelOp.Contains(*cp)) { + if (setRelOp.Contains(cp[1])) { + op += cp[1]; + cp++; + } + } else if (setLogicalOp.Contains(*cp)) { + if (setLogicalOp.Contains(cp[1])) { + op += cp[1]; + cp++; + } + } + tokens.push_back(op); + } + } + cp++; + } + + EvaluateTokens(tokens); + + // "0" or "" -> false else true + bool isFalse = tokens.empty() || + ((tokens.size() == 1) && ((tokens[0] == "") || tokens[0] == "0")); + return !isFalse; } -LexerModule lmCPP(SCLEX_CPP, ColouriseCppDocSensitive, "cpp", FoldCppDoc, cppWordLists); -LexerModule lmCPPNoCase(SCLEX_CPPNOCASE, ColouriseCppDocInsensitive, "cppnocase", FoldCppDoc, cppWordLists); +LexerModule lmCPP(SCLEX_CPP, LexerCPP::LexerFactoryCPP, "cpp", cppWordLists); +LexerModule lmCPPNoCase(SCLEX_CPPNOCASE, LexerCPP::LexerFactoryCPPInsensitive, "cppnocase", cppWordLists); |