mirror of
				https://github.com/notepad-plus-plus/notepad-plus-plus.git
				synced 2025-10-31 11:34:05 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			474 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			474 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Scintilla source code edit control
 | |
| /** @file LexKVIrc.cxx
 | |
|  ** Lexer for KVIrc script.
 | |
|  **/
 | |
| // Copyright 2013 by OmegaPhil <OmegaPhil+scintilla@gmail.com>, based in
 | |
| // part from LexPython Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
 | |
| // and LexCmake Copyright 2007 by Cristian Adam <cristian [dot] adam [at] gmx [dot] net>
 | |
| 
 | |
| // 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 "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
 | |
| 
 | |
| 
 | |
| /* 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(unsigned int startPos, int 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 '#<channel
 | |
|                  * name>' 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];
 | |
|                     int wordLen = sc.currentPos - styler.GetStartSegment();
 | |
|                     if (wordLen > 99)
 | |
|                         wordLen = 99;  /* Include '\0' in buffer */
 | |
|                     int 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(unsigned int startPos, int 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*/
 | |
|     int currentLine = styler.GetLine(startPos);
 | |
| 
 | |
|     /* Obtaining starting character - indentation is done on a line basis,
 | |
|      * not character */
 | |
|     unsigned int 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 (unsigned int 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);
 |