// Scintilla source code edit control /** @file LexKVIrc.cxx ** Lexer for KVIrc script. **/ // Copyright 2013 by OmegaPhil , based in // part from LexPython Copyright 1998-2002 by Neil Hodgson // and LexCmake Copyright 2007 by Cristian Adam // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include #include #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" using namespace Scintilla; /* KVIrc Script syntactic rules: http://www.kvirc.net/doc/doc_syntactic_rules.html */ /* Utility functions */ static inline bool IsAWordChar(int ch) { /* Keyword list includes modules, i.e. words including '.', and * alias namespaces include ':' */ return (ch < 0x80) && (isalnum(ch) || ch == '_' || ch == '.' || ch == ':'); } static inline bool IsAWordStart(int ch) { /* Functions (start with '$') are treated separately to keywords */ return (ch < 0x80) && (isalnum(ch) || ch == '_' ); } /* Interface function called by Scintilla to request some text to be syntax highlighted */ static void ColouriseKVIrcDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *keywordlists[], Accessor &styler) { /* Fetching style context */ StyleContext sc(startPos, length, initStyle, styler); /* Accessing keywords and function-marking keywords */ WordList &keywords = *keywordlists[0]; WordList &functionKeywords = *keywordlists[1]; /* Looping for all characters - only automatically moving forward * when asked for (transitions leaving strings and keywords do this * already) */ bool next = true; for( ; sc.More(); next ? sc.Forward() : (void)0 ) { /* Resetting next */ next = true; /* Dealing with different states */ switch (sc.state) { case SCE_KVIRC_DEFAULT: /* Detecting single-line comments * Unfortunately KVIrc script allows raw '#' to be used, and appending # to an array returns * its length... * Going for a compromise where single line comments not * starting on a newline are allowed in all cases except * when they are preceeded with an opening bracket or comma * (this will probably be the most common style a valid * string-less channel name will be used with), with the * array length case included */ if ( (sc.ch == '#' && sc.atLineStart) || (sc.ch == '#' && ( sc.chPrev != '(' && sc.chPrev != ',' && sc.chPrev != ']') ) ) { sc.SetState(SCE_KVIRC_COMMENT); break; } /* Detecting multi-line comments */ if (sc.Match('/', '*')) { sc.SetState(SCE_KVIRC_COMMENTBLOCK); break; } /* Detecting strings */ if (sc.ch == '"') { sc.SetState(SCE_KVIRC_STRING); break; } /* Detecting functions */ if (sc.ch == '$') { sc.SetState(SCE_KVIRC_FUNCTION); break; } /* Detecting variables */ if (sc.ch == '%') { sc.SetState(SCE_KVIRC_VARIABLE); break; } /* Detecting numbers - isdigit is unsafe as it does not * validate, use CharacterSet.h functions */ if (IsADigit(sc.ch)) { sc.SetState(SCE_KVIRC_NUMBER); break; } /* Detecting words */ if (IsAWordStart(sc.ch) && IsAWordChar(sc.chNext)) { sc.SetState(SCE_KVIRC_WORD); sc.Forward(); break; } /* Detecting operators */ if (isoperator(sc.ch)) { sc.SetState(SCE_KVIRC_OPERATOR); break; } break; case SCE_KVIRC_COMMENT: /* Breaking out of single line comment when a newline * is introduced */ if (sc.ch == '\r' || sc.ch == '\n') { sc.SetState(SCE_KVIRC_DEFAULT); break; } break; case SCE_KVIRC_COMMENTBLOCK: /* Detecting end of multi-line comment */ if (sc.Match('*', '/')) { // Moving the current position forward two characters // so that '*/' is included in the comment sc.Forward(2); sc.SetState(SCE_KVIRC_DEFAULT); /* Comment has been exited and the current position * moved forward, yet the new current character * has yet to be defined - loop without moving * forward again */ next = false; break; } break; case SCE_KVIRC_STRING: /* Detecting end of string - closing speechmarks */ if (sc.ch == '"') { /* Allowing escaped speechmarks to pass */ if (sc.chPrev == '\\') break; /* Moving the current position forward to capture the * terminating speechmarks, and ending string */ sc.ForwardSetState(SCE_KVIRC_DEFAULT); /* String has been exited and the current position * moved forward, yet the new current character * has yet to be defined - loop without moving * forward again */ next = false; break; } /* Functions and variables are now highlighted in strings * Detecting functions */ if (sc.ch == '$') { /* Allowing escaped functions to pass */ if (sc.chPrev == '\\') break; sc.SetState(SCE_KVIRC_STRING_FUNCTION); break; } /* Detecting variables */ if (sc.ch == '%') { /* Allowing escaped variables to pass */ if (sc.chPrev == '\\') break; sc.SetState(SCE_KVIRC_STRING_VARIABLE); break; } /* Breaking out of a string when a newline is introduced */ if (sc.ch == '\r' || sc.ch == '\n') { /* Allowing escaped newlines */ if (sc.chPrev == '\\') break; sc.SetState(SCE_KVIRC_DEFAULT); break; } break; case SCE_KVIRC_FUNCTION: case SCE_KVIRC_VARIABLE: /* Detecting the end of a function/variable (word) */ if (!IsAWordChar(sc.ch)) { sc.SetState(SCE_KVIRC_DEFAULT); /* Word has been exited yet the current character * has yet to be defined - loop without moving * forward again */ next = false; break; } break; case SCE_KVIRC_STRING_FUNCTION: case SCE_KVIRC_STRING_VARIABLE: /* A function or variable in a string * Detecting the end of a function/variable (word) */ if (!IsAWordChar(sc.ch)) { sc.SetState(SCE_KVIRC_STRING); /* Word has been exited yet the current character * has yet to be defined - loop without moving * forward again */ next = false; break; } break; case SCE_KVIRC_NUMBER: /* Detecting the end of a number */ if (!IsADigit(sc.ch)) { sc.SetState(SCE_KVIRC_DEFAULT); /* Number has been exited yet the current character * has yet to be defined - loop without moving * forward */ next = false; break; } break; case SCE_KVIRC_OPERATOR: /* Because '%' is an operator but is also the marker for * a variable, I need to always treat operators as single * character strings and therefore redo their detection * after every character */ sc.SetState(SCE_KVIRC_DEFAULT); /* Operator has been exited yet the current character * has yet to be defined - loop without moving * forward */ next = false; break; case SCE_KVIRC_WORD: /* Detecting the end of a word */ if (!IsAWordChar(sc.ch)) { /* Checking if the word was actually a keyword - * fetching the current word, NULL-terminated like * the keyword list */ char s[100]; Sci_Position wordLen = sc.currentPos - styler.GetStartSegment(); if (wordLen > 99) wordLen = 99; /* Include '\0' in buffer */ Sci_Position i; for( i = 0; i < wordLen; ++i ) { s[i] = styler.SafeGetCharAt( styler.GetStartSegment() + i ); } s[wordLen] = '\0'; /* Actually detecting keywords and fixing the state */ if (keywords.InList(s)) { /* The SetState call actually commits the * previous keyword state */ sc.ChangeState(SCE_KVIRC_KEYWORD); } else if (functionKeywords.InList(s)) { // Detecting function keywords and fixing the state sc.ChangeState(SCE_KVIRC_FUNCTION_KEYWORD); } /* Transitioning to default and committing the previous * word state */ sc.SetState(SCE_KVIRC_DEFAULT); /* Word has been exited yet the current character * has yet to be defined - loop without moving * forward again */ next = false; break; } break; } } /* Indicating processing is complete */ sc.Complete(); } static void FoldKVIrcDoc(Sci_PositionU startPos, Sci_Position length, int /*initStyle - unused*/, WordList *[], Accessor &styler) { /* Based on CMake's folder */ /* Exiting if folding isnt enabled */ if ( styler.GetPropertyInt("fold") == 0 ) return; /* Obtaining current line number*/ Sci_Position currentLine = styler.GetLine(startPos); /* Obtaining starting character - indentation is done on a line basis, * not character */ Sci_PositionU safeStartPos = styler.LineStart( currentLine ); /* Initialising current level - this is defined as indentation level * in the low 12 bits, with flag bits in the upper four bits. * It looks like two indentation states are maintained in the returned * 32bit value - 'nextLevel' in the most-significant bits, 'currentLevel' * in the least-significant bits. Since the next level is the most * up to date, this must refer to the current state of indentation. * So the code bitshifts the old current level out of existence to * get at the actual current state of indentation * Based on the LexerCPP.cxx line 958 comment */ int currentLevel = SC_FOLDLEVELBASE; if (currentLine > 0) currentLevel = styler.LevelAt(currentLine - 1) >> 16; int nextLevel = currentLevel; // Looping for characters in range for (Sci_PositionU i = safeStartPos; i < startPos + length; ++i) { /* Folding occurs after syntax highlighting, meaning Scintilla * already knows where the comments are * Fetching the current state */ int state = styler.StyleAt(i) & 31; switch( styler.SafeGetCharAt(i) ) { case '{': /* Indenting only when the braces are not contained in * a comment */ if (state != SCE_KVIRC_COMMENT && state != SCE_KVIRC_COMMENTBLOCK) ++nextLevel; break; case '}': /* Outdenting only when the braces are not contained in * a comment */ if (state != SCE_KVIRC_COMMENT && state != SCE_KVIRC_COMMENTBLOCK) --nextLevel; break; case '\n': case '\r': /* Preparing indentation information to return - combining * current and next level data */ int lev = currentLevel | nextLevel << 16; /* If the next level increases the indent level, mark the * current line as a fold point - current level data is * in the least significant bits */ if (nextLevel > currentLevel ) lev |= SC_FOLDLEVELHEADERFLAG; /* Updating indentation level if needed */ if (lev != styler.LevelAt(currentLine)) styler.SetLevel(currentLine, lev); /* Updating variables */ ++currentLine; currentLevel = nextLevel; /* Dealing with problematic Windows newlines - * incrementing to avoid the extra newline breaking the * fold point */ if (styler.SafeGetCharAt(i) == '\r' && styler.SafeGetCharAt(i + 1) == '\n') ++i; break; } } /* At this point the data has ended, so presumably the end of the line? * Preparing indentation information to return - combining current * and next level data */ int lev = currentLevel | nextLevel << 16; /* If the next level increases the indent level, mark the current * line as a fold point - current level data is in the least * significant bits */ if (nextLevel > currentLevel ) lev |= SC_FOLDLEVELHEADERFLAG; /* Updating indentation level if needed */ if (lev != styler.LevelAt(currentLine)) styler.SetLevel(currentLine, lev); } /* Registering wordlists */ static const char *const kvircWordListDesc[] = { "primary", "function_keywords", 0 }; /* Registering functions and wordlists */ LexerModule lmKVIrc(SCLEX_KVIRC, ColouriseKVIrcDoc, "kvirc", FoldKVIrcDoc, kvircWordListDesc);