You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
474 lines
16 KiB
474 lines
16 KiB
// 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 <string> |
|
#include <string_view> |
|
|
|
#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 Lexilla; |
|
|
|
|
|
/* 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 '#<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]; |
|
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 */ |
|
extern const LexerModule lmKVIrc(SCLEX_KVIRC, ColouriseKVIrcDoc, "kvirc", FoldKVIrcDoc, |
|
kvircWordListDesc);
|
|
|