LexKVIrc.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. // Scintilla source code edit control
  2. /** @file LexKVIrc.cxx
  3. ** Lexer for KVIrc script.
  4. **/
  5. // Copyright 2013 by OmegaPhil <OmegaPhil+scintilla@gmail.com>, based in
  6. // part from LexPython Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
  7. // and LexCmake Copyright 2007 by Cristian Adam <cristian [dot] adam [at] gmx [dot] net>
  8. // The License.txt file describes the conditions under which this software may be distributed.
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <stdio.h>
  12. #include <stdarg.h>
  13. #include <assert.h>
  14. #include <ctype.h>
  15. #include "ILexer.h"
  16. #include "Scintilla.h"
  17. #include "SciLexer.h"
  18. #include "WordList.h"
  19. #include "LexAccessor.h"
  20. #include "Accessor.h"
  21. #include "StyleContext.h"
  22. #include "CharacterSet.h"
  23. #include "LexerModule.h"
  24. #ifdef SCI_NAMESPACE
  25. using namespace Scintilla;
  26. #endif
  27. /* KVIrc Script syntactic rules: http://www.kvirc.net/doc/doc_syntactic_rules.html */
  28. /* Utility functions */
  29. static inline bool IsAWordChar(int ch) {
  30. /* Keyword list includes modules, i.e. words including '.', and
  31. * alias namespaces include ':' */
  32. return (ch < 0x80) && (isalnum(ch) || ch == '_' || ch == '.'
  33. || ch == ':');
  34. }
  35. static inline bool IsAWordStart(int ch) {
  36. /* Functions (start with '$') are treated separately to keywords */
  37. return (ch < 0x80) && (isalnum(ch) || ch == '_' );
  38. }
  39. /* Interface function called by Scintilla to request some text to be
  40. syntax highlighted */
  41. static void ColouriseKVIrcDoc(Sci_PositionU startPos, Sci_Position length,
  42. int initStyle, WordList *keywordlists[],
  43. Accessor &styler)
  44. {
  45. /* Fetching style context */
  46. StyleContext sc(startPos, length, initStyle, styler);
  47. /* Accessing keywords and function-marking keywords */
  48. WordList &keywords = *keywordlists[0];
  49. WordList &functionKeywords = *keywordlists[1];
  50. /* Looping for all characters - only automatically moving forward
  51. * when asked for (transitions leaving strings and keywords do this
  52. * already) */
  53. bool next = true;
  54. for( ; sc.More(); next ? sc.Forward() : (void)0 )
  55. {
  56. /* Resetting next */
  57. next = true;
  58. /* Dealing with different states */
  59. switch (sc.state)
  60. {
  61. case SCE_KVIRC_DEFAULT:
  62. /* Detecting single-line comments
  63. * Unfortunately KVIrc script allows raw '#<channel
  64. * name>' to be used, and appending # to an array returns
  65. * its length...
  66. * Going for a compromise where single line comments not
  67. * starting on a newline are allowed in all cases except
  68. * when they are preceeded with an opening bracket or comma
  69. * (this will probably be the most common style a valid
  70. * string-less channel name will be used with), with the
  71. * array length case included
  72. */
  73. if (
  74. (sc.ch == '#' && sc.atLineStart) ||
  75. (sc.ch == '#' && (
  76. sc.chPrev != '(' && sc.chPrev != ',' &&
  77. sc.chPrev != ']')
  78. )
  79. )
  80. {
  81. sc.SetState(SCE_KVIRC_COMMENT);
  82. break;
  83. }
  84. /* Detecting multi-line comments */
  85. if (sc.Match('/', '*'))
  86. {
  87. sc.SetState(SCE_KVIRC_COMMENTBLOCK);
  88. break;
  89. }
  90. /* Detecting strings */
  91. if (sc.ch == '"')
  92. {
  93. sc.SetState(SCE_KVIRC_STRING);
  94. break;
  95. }
  96. /* Detecting functions */
  97. if (sc.ch == '$')
  98. {
  99. sc.SetState(SCE_KVIRC_FUNCTION);
  100. break;
  101. }
  102. /* Detecting variables */
  103. if (sc.ch == '%')
  104. {
  105. sc.SetState(SCE_KVIRC_VARIABLE);
  106. break;
  107. }
  108. /* Detecting numbers - isdigit is unsafe as it does not
  109. * validate, use CharacterSet.h functions */
  110. if (IsADigit(sc.ch))
  111. {
  112. sc.SetState(SCE_KVIRC_NUMBER);
  113. break;
  114. }
  115. /* Detecting words */
  116. if (IsAWordStart(sc.ch) && IsAWordChar(sc.chNext))
  117. {
  118. sc.SetState(SCE_KVIRC_WORD);
  119. sc.Forward();
  120. break;
  121. }
  122. /* Detecting operators */
  123. if (isoperator(sc.ch))
  124. {
  125. sc.SetState(SCE_KVIRC_OPERATOR);
  126. break;
  127. }
  128. break;
  129. case SCE_KVIRC_COMMENT:
  130. /* Breaking out of single line comment when a newline
  131. * is introduced */
  132. if (sc.ch == '\r' || sc.ch == '\n')
  133. {
  134. sc.SetState(SCE_KVIRC_DEFAULT);
  135. break;
  136. }
  137. break;
  138. case SCE_KVIRC_COMMENTBLOCK:
  139. /* Detecting end of multi-line comment */
  140. if (sc.Match('*', '/'))
  141. {
  142. // Moving the current position forward two characters
  143. // so that '*/' is included in the comment
  144. sc.Forward(2);
  145. sc.SetState(SCE_KVIRC_DEFAULT);
  146. /* Comment has been exited and the current position
  147. * moved forward, yet the new current character
  148. * has yet to be defined - loop without moving
  149. * forward again */
  150. next = false;
  151. break;
  152. }
  153. break;
  154. case SCE_KVIRC_STRING:
  155. /* Detecting end of string - closing speechmarks */
  156. if (sc.ch == '"')
  157. {
  158. /* Allowing escaped speechmarks to pass */
  159. if (sc.chPrev == '\\')
  160. break;
  161. /* Moving the current position forward to capture the
  162. * terminating speechmarks, and ending string */
  163. sc.ForwardSetState(SCE_KVIRC_DEFAULT);
  164. /* String has been exited and the current position
  165. * moved forward, yet the new current character
  166. * has yet to be defined - loop without moving
  167. * forward again */
  168. next = false;
  169. break;
  170. }
  171. /* Functions and variables are now highlighted in strings
  172. * Detecting functions */
  173. if (sc.ch == '$')
  174. {
  175. /* Allowing escaped functions to pass */
  176. if (sc.chPrev == '\\')
  177. break;
  178. sc.SetState(SCE_KVIRC_STRING_FUNCTION);
  179. break;
  180. }
  181. /* Detecting variables */
  182. if (sc.ch == '%')
  183. {
  184. /* Allowing escaped variables to pass */
  185. if (sc.chPrev == '\\')
  186. break;
  187. sc.SetState(SCE_KVIRC_STRING_VARIABLE);
  188. break;
  189. }
  190. /* Breaking out of a string when a newline is introduced */
  191. if (sc.ch == '\r' || sc.ch == '\n')
  192. {
  193. /* Allowing escaped newlines */
  194. if (sc.chPrev == '\\')
  195. break;
  196. sc.SetState(SCE_KVIRC_DEFAULT);
  197. break;
  198. }
  199. break;
  200. case SCE_KVIRC_FUNCTION:
  201. case SCE_KVIRC_VARIABLE:
  202. /* Detecting the end of a function/variable (word) */
  203. if (!IsAWordChar(sc.ch))
  204. {
  205. sc.SetState(SCE_KVIRC_DEFAULT);
  206. /* Word has been exited yet the current character
  207. * has yet to be defined - loop without moving
  208. * forward again */
  209. next = false;
  210. break;
  211. }
  212. break;
  213. case SCE_KVIRC_STRING_FUNCTION:
  214. case SCE_KVIRC_STRING_VARIABLE:
  215. /* A function or variable in a string
  216. * Detecting the end of a function/variable (word) */
  217. if (!IsAWordChar(sc.ch))
  218. {
  219. sc.SetState(SCE_KVIRC_STRING);
  220. /* Word has been exited yet the current character
  221. * has yet to be defined - loop without moving
  222. * forward again */
  223. next = false;
  224. break;
  225. }
  226. break;
  227. case SCE_KVIRC_NUMBER:
  228. /* Detecting the end of a number */
  229. if (!IsADigit(sc.ch))
  230. {
  231. sc.SetState(SCE_KVIRC_DEFAULT);
  232. /* Number has been exited yet the current character
  233. * has yet to be defined - loop without moving
  234. * forward */
  235. next = false;
  236. break;
  237. }
  238. break;
  239. case SCE_KVIRC_OPERATOR:
  240. /* Because '%' is an operator but is also the marker for
  241. * a variable, I need to always treat operators as single
  242. * character strings and therefore redo their detection
  243. * after every character */
  244. sc.SetState(SCE_KVIRC_DEFAULT);
  245. /* Operator has been exited yet the current character
  246. * has yet to be defined - loop without moving
  247. * forward */
  248. next = false;
  249. break;
  250. case SCE_KVIRC_WORD:
  251. /* Detecting the end of a word */
  252. if (!IsAWordChar(sc.ch))
  253. {
  254. /* Checking if the word was actually a keyword -
  255. * fetching the current word, NULL-terminated like
  256. * the keyword list */
  257. char s[100];
  258. Sci_Position wordLen = sc.currentPos - styler.GetStartSegment();
  259. if (wordLen > 99)
  260. wordLen = 99; /* Include '\0' in buffer */
  261. Sci_Position i;
  262. for( i = 0; i < wordLen; ++i )
  263. {
  264. s[i] = styler.SafeGetCharAt( styler.GetStartSegment() + i );
  265. }
  266. s[wordLen] = '\0';
  267. /* Actually detecting keywords and fixing the state */
  268. if (keywords.InList(s))
  269. {
  270. /* The SetState call actually commits the
  271. * previous keyword state */
  272. sc.ChangeState(SCE_KVIRC_KEYWORD);
  273. }
  274. else if (functionKeywords.InList(s))
  275. {
  276. // Detecting function keywords and fixing the state
  277. sc.ChangeState(SCE_KVIRC_FUNCTION_KEYWORD);
  278. }
  279. /* Transitioning to default and committing the previous
  280. * word state */
  281. sc.SetState(SCE_KVIRC_DEFAULT);
  282. /* Word has been exited yet the current character
  283. * has yet to be defined - loop without moving
  284. * forward again */
  285. next = false;
  286. break;
  287. }
  288. break;
  289. }
  290. }
  291. /* Indicating processing is complete */
  292. sc.Complete();
  293. }
  294. static void FoldKVIrcDoc(Sci_PositionU startPos, Sci_Position length, int /*initStyle - unused*/,
  295. WordList *[], Accessor &styler)
  296. {
  297. /* Based on CMake's folder */
  298. /* Exiting if folding isnt enabled */
  299. if ( styler.GetPropertyInt("fold") == 0 )
  300. return;
  301. /* Obtaining current line number*/
  302. Sci_Position currentLine = styler.GetLine(startPos);
  303. /* Obtaining starting character - indentation is done on a line basis,
  304. * not character */
  305. Sci_PositionU safeStartPos = styler.LineStart( currentLine );
  306. /* Initialising current level - this is defined as indentation level
  307. * in the low 12 bits, with flag bits in the upper four bits.
  308. * It looks like two indentation states are maintained in the returned
  309. * 32bit value - 'nextLevel' in the most-significant bits, 'currentLevel'
  310. * in the least-significant bits. Since the next level is the most
  311. * up to date, this must refer to the current state of indentation.
  312. * So the code bitshifts the old current level out of existence to
  313. * get at the actual current state of indentation
  314. * Based on the LexerCPP.cxx line 958 comment */
  315. int currentLevel = SC_FOLDLEVELBASE;
  316. if (currentLine > 0)
  317. currentLevel = styler.LevelAt(currentLine - 1) >> 16;
  318. int nextLevel = currentLevel;
  319. // Looping for characters in range
  320. for (Sci_PositionU i = safeStartPos; i < startPos + length; ++i)
  321. {
  322. /* Folding occurs after syntax highlighting, meaning Scintilla
  323. * already knows where the comments are
  324. * Fetching the current state */
  325. int state = styler.StyleAt(i) & 31;
  326. switch( styler.SafeGetCharAt(i) )
  327. {
  328. case '{':
  329. /* Indenting only when the braces are not contained in
  330. * a comment */
  331. if (state != SCE_KVIRC_COMMENT &&
  332. state != SCE_KVIRC_COMMENTBLOCK)
  333. ++nextLevel;
  334. break;
  335. case '}':
  336. /* Outdenting only when the braces are not contained in
  337. * a comment */
  338. if (state != SCE_KVIRC_COMMENT &&
  339. state != SCE_KVIRC_COMMENTBLOCK)
  340. --nextLevel;
  341. break;
  342. case '\n':
  343. case '\r':
  344. /* Preparing indentation information to return - combining
  345. * current and next level data */
  346. int lev = currentLevel | nextLevel << 16;
  347. /* If the next level increases the indent level, mark the
  348. * current line as a fold point - current level data is
  349. * in the least significant bits */
  350. if (nextLevel > currentLevel )
  351. lev |= SC_FOLDLEVELHEADERFLAG;
  352. /* Updating indentation level if needed */
  353. if (lev != styler.LevelAt(currentLine))
  354. styler.SetLevel(currentLine, lev);
  355. /* Updating variables */
  356. ++currentLine;
  357. currentLevel = nextLevel;
  358. /* Dealing with problematic Windows newlines -
  359. * incrementing to avoid the extra newline breaking the
  360. * fold point */
  361. if (styler.SafeGetCharAt(i) == '\r' &&
  362. styler.SafeGetCharAt(i + 1) == '\n')
  363. ++i;
  364. break;
  365. }
  366. }
  367. /* At this point the data has ended, so presumably the end of the line?
  368. * Preparing indentation information to return - combining current
  369. * and next level data */
  370. int lev = currentLevel | nextLevel << 16;
  371. /* If the next level increases the indent level, mark the current
  372. * line as a fold point - current level data is in the least
  373. * significant bits */
  374. if (nextLevel > currentLevel )
  375. lev |= SC_FOLDLEVELHEADERFLAG;
  376. /* Updating indentation level if needed */
  377. if (lev != styler.LevelAt(currentLine))
  378. styler.SetLevel(currentLine, lev);
  379. }
  380. /* Registering wordlists */
  381. static const char *const kvircWordListDesc[] = {
  382. "primary",
  383. "function_keywords",
  384. 0
  385. };
  386. /* Registering functions and wordlists */
  387. LexerModule lmKVIrc(SCLEX_KVIRC, ColouriseKVIrcDoc, "kvirc", FoldKVIrcDoc,
  388. kvircWordListDesc);