1// Scintilla source code edit control
2/** @file LexVisualProlog.cxx
3** Lexer for Visual Prolog.
4**/
5// Author Thomas Linder Puls, Prolog Development Denter A/S, http://www.visual-prolog.com
6// Based on Lexer for C++, C, Java, and JavaScript.
7// Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
8// The License.txt file describes the conditions under which this software may be distributed.
9
10// The line state contains:
11// In SCE_VISUALPROLOG_STRING_VERBATIM_EOL (i.e. multiline string literal): The closingQuote.
12// else (for SCE_VISUALPROLOG_COMMENT_BLOCK): The comment nesting level
13
14#include <stdlib.h>
15#include <string.h>
16#include <stdio.h>
17#include <stdarg.h>
18#include <assert.h>
19#include <ctype.h>
20
21#ifdef _MSC_VER
22#pragma warning(disable: 4786)
23#endif
24
25#include <string>
26#include <string_view>
27#include <vector>
28#include <map>
29#include <algorithm>
30#include <functional>
31
32#include "ILexer.h"
33#include "Scintilla.h"
34#include "SciLexer.h"
35
36#include "WordList.h"
37#include "LexAccessor.h"
38#include "Accessor.h"
39#include "StyleContext.h"
40#include "CharacterSet.h"
41#include "CharacterCategory.h"
42#include "LexerModule.h"
43#include "OptionSet.h"
44#include "DefaultLexer.h"
45
46using namespace Scintilla;
47using namespace Lexilla;
48
49// Options used for LexerVisualProlog
50struct OptionsVisualProlog {
51 OptionsVisualProlog() {
52 }
53};
54
55static const char *const visualPrologWordLists[] = {
56 "Major keywords (class, predicates, ...)",
57 "Minor keywords (if, then, try, ...)",
58 "Directive keywords without the '#' (include, requires, ...)",
59 "Documentation keywords without the '@' (short, detail, ...)",
60 0,
61};
62
63struct OptionSetVisualProlog : public OptionSet<OptionsVisualProlog> {
64 OptionSetVisualProlog() {
65 DefineWordListSets(visualPrologWordLists);
66 }
67};
68
69class LexerVisualProlog : public DefaultLexer {
70 WordList majorKeywords;
71 WordList minorKeywords;
72 WordList directiveKeywords;
73 WordList docKeywords;
74 OptionsVisualProlog options;
75 OptionSetVisualProlog osVisualProlog;
76public:
77 LexerVisualProlog() : DefaultLexer("visualprolog", SCLEX_VISUALPROLOG) {
78 }
79 virtual ~LexerVisualProlog() {
80 }
81 void SCI_METHOD Release() override {
82 delete this;
83 }
84 int SCI_METHOD Version() const override {
85 return lvRelease5;
86 }
87 const char * SCI_METHOD PropertyNames() override {
88 return osVisualProlog.PropertyNames();
89 }
90 int SCI_METHOD PropertyType(const char *name) override {
91 return osVisualProlog.PropertyType(name);
92 }
93 const char * SCI_METHOD DescribeProperty(const char *name) override {
94 return osVisualProlog.DescribeProperty(name);
95 }
96 Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override;
97 const char * SCI_METHOD PropertyGet(const char *key) override {
98 return osVisualProlog.PropertyGet(key);
99 }
100 const char * SCI_METHOD DescribeWordListSets() override {
101 return osVisualProlog.DescribeWordListSets();
102 }
103 Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override;
104 void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
105 void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
106
107 void * SCI_METHOD PrivateCall(int, void *) override {
108 return 0;
109 }
110
111 static ILexer5 *LexerFactoryVisualProlog() {
112 return new LexerVisualProlog();
113 }
114};
115
116Sci_Position SCI_METHOD LexerVisualProlog::PropertySet(const char *key, const char *val) {
117 if (osVisualProlog.PropertySet(&options, key, val)) {
118 return 0;
119 }
120 return -1;
121}
122
123Sci_Position SCI_METHOD LexerVisualProlog::WordListSet(int n, const char *wl) {
124 WordList *wordListN = 0;
125 switch (n) {
126 case 0:
127 wordListN = &majorKeywords;
128 break;
129 case 1:
130 wordListN = &minorKeywords;
131 break;
132 case 2:
133 wordListN = &directiveKeywords;
134 break;
135 case 3:
136 wordListN = &docKeywords;
137 break;
138 }
139 Sci_Position firstModification = -1;
140 if (wordListN) {
141 WordList wlNew;
142 wlNew.Set(wl);
143 if (*wordListN != wlNew) {
144 wordListN->Set(wl);
145 firstModification = 0;
146 }
147 }
148 return firstModification;
149}
150
151// Functor used to truncate history
152struct After {
153 Sci_Position line;
154 After(Sci_Position line_) : line(line_) {}
155};
156
157static bool isLowerLetter(int ch){
158 return ccLl == CategoriseCharacter(ch);
159}
160
161static bool isUpperLetter(int ch){
162 return ccLu == CategoriseCharacter(ch);
163}
164
165static bool isAlphaNum(int ch){
166 CharacterCategory cc = CategoriseCharacter(ch);
167 return (ccLu == cc || ccLl == cc || ccLt == cc || ccLm == cc || ccLo == cc || ccNd == cc || ccNl == cc || ccNo == cc);
168}
169
170static bool isStringVerbatimOpenClose(int ch){
171 CharacterCategory cc = CategoriseCharacter(ch);
172 return (ccPc <= cc && cc <= ccSo);
173}
174
175static bool isIdChar(int ch){
176 return ('_') == ch || isAlphaNum(ch);
177}
178
179static bool isOpenStringVerbatim(int next, int &closingQuote){
180 switch (next) {
181 case L'<':
182 closingQuote = L'>';
183 return true;
184 case L'>':
185 closingQuote = L'<';
186 return true;
187 case L'(':
188 closingQuote = L')';
189 return true;
190 case L')':
191 closingQuote = L'(';
192 return true;
193 case L'[':
194 closingQuote = L']';
195 return true;
196 case L']':
197 closingQuote = L'[';
198 return true;
199 case L'{':
200 closingQuote = L'}';
201 return true;
202 case L'}':
203 closingQuote = L'{';
204 return true;
205 case L'_':
206 case L'.':
207 case L',':
208 case L';':
209 return false;
210 default:
211 if (isStringVerbatimOpenClose(next)) {
212 closingQuote = next;
213 return true;
214 } else {
215 return false;
216 }
217 }
218}
219
220// Look ahead to see which colour "end" should have (takes colour after the following keyword)
221static void endLookAhead(char s[], LexAccessor &styler, Sci_Position start) {
222 char ch = styler.SafeGetCharAt(start, '\n');
223 while (' ' == ch) {
224 start++;
225 ch = styler.SafeGetCharAt(start, '\n');
226 }
227 Sci_Position i = 0;
228 while (i < 100 && isLowerLetter(ch)){
229 s[i] = ch;
230 i++;
231 ch = styler.SafeGetCharAt(start + i, '\n');
232 }
233 s[i] = '\0';
234}
235
236static void forwardEscapeLiteral(StyleContext &sc, int EscapeState) {
237 sc.Forward();
238 if (sc.Match('"') || sc.Match('\'') || sc.Match('\\') || sc.Match('n') || sc.Match('l') || sc.Match('r') || sc.Match('t')) {
239 sc.ChangeState(EscapeState);
240 } else if (sc.Match('u')) {
241 if (IsADigit(sc.chNext, 16)) {
242 sc.Forward();
243 if (IsADigit(sc.chNext, 16)) {
244 sc.Forward();
245 if (IsADigit(sc.chNext, 16)) {
246 sc.Forward();
247 if (IsADigit(sc.chNext, 16)) {
248 sc.Forward();
249 sc.ChangeState(EscapeState);
250 }
251 }
252 }
253 }
254 }
255}
256
257void SCI_METHOD LexerVisualProlog::Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
258 LexAccessor styler(pAccess);
259 CharacterSet setDoxygen(CharacterSet::setAlpha, "");
260 CharacterSet setNumber(CharacterSet::setNone, "0123456789abcdefABCDEFxoXO");
261
262 StyleContext sc(startPos, length, initStyle, styler, 0x7f);
263
264 int styleBeforeDocKeyword = SCE_VISUALPROLOG_DEFAULT;
265 Sci_Position currentLine = styler.GetLine(startPos);
266
267 int closingQuote = '"';
268 int nestLevel = 0;
269 if (currentLine >= 1)
270 {
271 nestLevel = styler.GetLineState(currentLine - 1);
272 closingQuote = nestLevel;
273 }
274
275 // Truncate ppDefineHistory before current line
276
277 for (; sc.More(); sc.Forward()) {
278
279 // Determine if the current state should terminate.
280 switch (sc.state) {
281 case SCE_VISUALPROLOG_OPERATOR:
282 sc.SetState(SCE_VISUALPROLOG_DEFAULT);
283 break;
284 case SCE_VISUALPROLOG_NUMBER:
285 // We accept almost anything because of hex. and number suffixes
286 if (!(setNumber.Contains(sc.ch)) || (sc.Match('.') && IsADigit(sc.chNext))) {
287 sc.SetState(SCE_VISUALPROLOG_DEFAULT);
288 }
289 break;
290 case SCE_VISUALPROLOG_IDENTIFIER:
291 if (!isIdChar(sc.ch)) {
292 char s[1000];
293 sc.GetCurrent(s, sizeof(s));
294 if (0 == strcmp(s, "end")) {
295 endLookAhead(s, styler, sc.currentPos);
296 }
297 if (majorKeywords.InList(s)) {
298 sc.ChangeState(SCE_VISUALPROLOG_KEY_MAJOR);
299 } else if (minorKeywords.InList(s)) {
300 sc.ChangeState(SCE_VISUALPROLOG_KEY_MINOR);
301 }
302 sc.SetState(SCE_VISUALPROLOG_DEFAULT);
303 }
304 break;
305 case SCE_VISUALPROLOG_VARIABLE:
306 case SCE_VISUALPROLOG_ANONYMOUS:
307 if (!isIdChar(sc.ch)) {
308 sc.SetState(SCE_VISUALPROLOG_DEFAULT);
309 }
310 break;
311 case SCE_VISUALPROLOG_KEY_DIRECTIVE:
312 if (!isLowerLetter(sc.ch)) {
313 char s[1000];
314 sc.GetCurrent(s, sizeof(s));
315 if (!directiveKeywords.InList(s+1)) {
316 sc.ChangeState(SCE_VISUALPROLOG_IDENTIFIER);
317 }
318 sc.SetState(SCE_VISUALPROLOG_DEFAULT);
319 }
320 break;
321 case SCE_VISUALPROLOG_COMMENT_BLOCK:
322 if (sc.Match('*', '/')) {
323 sc.Forward();
324 nestLevel--;
325 int nextState = (nestLevel == 0) ? SCE_VISUALPROLOG_DEFAULT : SCE_VISUALPROLOG_COMMENT_BLOCK;
326 sc.ForwardSetState(nextState);
327 } else if (sc.Match('/', '*')) {
328 sc.Forward();
329 nestLevel++;
330 } else if (sc.Match('@')) {
331 styleBeforeDocKeyword = sc.state;
332 sc.SetState(SCE_VISUALPROLOG_COMMENT_KEY_ERROR);
333 }
334 break;
335 case SCE_VISUALPROLOG_COMMENT_LINE:
336 if (sc.atLineEnd) {
337 int nextState = (nestLevel == 0) ? SCE_VISUALPROLOG_DEFAULT : SCE_VISUALPROLOG_COMMENT_BLOCK;
338 sc.SetState(nextState);
339 } else if (sc.Match('@')) {
340 styleBeforeDocKeyword = sc.state;
341 sc.SetState(SCE_VISUALPROLOG_COMMENT_KEY_ERROR);
342 }
343 break;
344 case SCE_VISUALPROLOG_COMMENT_KEY_ERROR:
345 if (!setDoxygen.Contains(sc.ch) || sc.atLineEnd) {
346 char s[1000];
347 sc.GetCurrent(s, sizeof(s));
348 if (docKeywords.InList(s+1)) {
349 sc.ChangeState(SCE_VISUALPROLOG_COMMENT_KEY);
350 }
351 if (SCE_VISUALPROLOG_COMMENT_LINE == styleBeforeDocKeyword && sc.atLineEnd) {
352 // end line comment
353 int nextState = (nestLevel == 0) ? SCE_VISUALPROLOG_DEFAULT : SCE_VISUALPROLOG_COMMENT_BLOCK;
354 sc.SetState(nextState);
355 } else {
356 sc.SetState(styleBeforeDocKeyword);
357 if (SCE_VISUALPROLOG_COMMENT_BLOCK == styleBeforeDocKeyword && sc.Match('*', '/')) {
358 // we have consumed the '*' if it comes immediately after the docKeyword
359 sc.Forward();
360 sc.Forward();
361 nestLevel--;
362 if (0 == nestLevel) {
363 sc.SetState(SCE_VISUALPROLOG_DEFAULT);
364 }
365 }
366 }
367 }
368 break;
369 case SCE_VISUALPROLOG_STRING_ESCAPE:
370 case SCE_VISUALPROLOG_STRING_ESCAPE_ERROR:
371 // return to SCE_VISUALPROLOG_STRING and treat as such (fall-through)
372 sc.SetState(SCE_VISUALPROLOG_STRING);
373 // Falls through.
374 case SCE_VISUALPROLOG_STRING:
375 if (sc.atLineEnd) {
376 sc.SetState(SCE_VISUALPROLOG_STRING_EOL_OPEN);
377 } else if (sc.Match(closingQuote)) {
378 sc.ForwardSetState(SCE_VISUALPROLOG_DEFAULT);
379 } else if (sc.Match('\\')) {
380 sc.SetState(SCE_VISUALPROLOG_STRING_ESCAPE_ERROR);
381 forwardEscapeLiteral(sc, SCE_VISUALPROLOG_STRING_ESCAPE);
382 }
383 break;
384 case SCE_VISUALPROLOG_STRING_EOL_OPEN:
385 if (sc.atLineStart) {
386 sc.SetState(SCE_VISUALPROLOG_DEFAULT);
387 }
388 break;
389 case SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL:
390 case SCE_VISUALPROLOG_STRING_VERBATIM_EOL:
391 // return to SCE_VISUALPROLOG_STRING_VERBATIM and treat as such (fall-through)
392 sc.SetState(SCE_VISUALPROLOG_STRING_VERBATIM);
393 // Falls through.
394 case SCE_VISUALPROLOG_STRING_VERBATIM:
395 if (sc.atLineEnd) {
396 sc.SetState(SCE_VISUALPROLOG_STRING_VERBATIM_EOL);
397 } else if (sc.Match(closingQuote)) {
398 if (closingQuote == sc.chNext) {
399 sc.SetState(SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL);
400 sc.Forward();
401 } else {
402 sc.ForwardSetState(SCE_VISUALPROLOG_DEFAULT);
403 }
404 }
405 break;
406 }
407
408 if (sc.atLineEnd) {
409 // Update the line state, so it can be seen by next line
410 int lineState = 0;
411 if (SCE_VISUALPROLOG_STRING_VERBATIM_EOL == sc.state) {
412 lineState = closingQuote;
413 } else if (SCE_VISUALPROLOG_COMMENT_BLOCK == sc.state) {
414 lineState = nestLevel;
415 }
416 styler.SetLineState(currentLine, lineState);
417 currentLine++;
418 }
419
420 // Determine if a new state should be entered.
421 if (sc.state == SCE_VISUALPROLOG_DEFAULT) {
422 if (sc.Match('@') && isOpenStringVerbatim(sc.chNext, closingQuote)) {
423 sc.SetState(SCE_VISUALPROLOG_STRING_VERBATIM);
424 sc.Forward();
425 } else if (IsADigit(sc.ch) || (sc.Match('.') && IsADigit(sc.chNext))) {
426 sc.SetState(SCE_VISUALPROLOG_NUMBER);
427 } else if (isLowerLetter(sc.ch)) {
428 sc.SetState(SCE_VISUALPROLOG_IDENTIFIER);
429 } else if (isUpperLetter(sc.ch)) {
430 sc.SetState(SCE_VISUALPROLOG_VARIABLE);
431 } else if (sc.Match('_')) {
432 sc.SetState(SCE_VISUALPROLOG_ANONYMOUS);
433 } else if (sc.Match('/', '*')) {
434 sc.SetState(SCE_VISUALPROLOG_COMMENT_BLOCK);
435 nestLevel = 1;
436 sc.Forward(); // Eat the * so it isn't used for the end of the comment
437 } else if (sc.Match('%')) {
438 sc.SetState(SCE_VISUALPROLOG_COMMENT_LINE);
439 } else if (sc.Match('\'')) {
440 closingQuote = '\'';
441 sc.SetState(SCE_VISUALPROLOG_STRING);
442 } else if (sc.Match('"')) {
443 closingQuote = '"';
444 sc.SetState(SCE_VISUALPROLOG_STRING);
445 } else if (sc.Match('#')) {
446 sc.SetState(SCE_VISUALPROLOG_KEY_DIRECTIVE);
447 } else if (isoperator(static_cast<char>(sc.ch)) || sc.Match('\\')) {
448 sc.SetState(SCE_VISUALPROLOG_OPERATOR);
449 }
450 }
451
452 }
453 sc.Complete();
454 styler.Flush();
455}
456
457// Store both the current line's fold level and the next lines in the
458// level store to make it easy to pick up with each increment
459// and to make it possible to fiddle the current level for "} else {".
460
461void SCI_METHOD LexerVisualProlog::Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
462
463 LexAccessor styler(pAccess);
464
465 Sci_PositionU endPos = startPos + length;
466 int visibleChars = 0;
467 Sci_Position currentLine = styler.GetLine(startPos);
468 int levelCurrent = SC_FOLDLEVELBASE;
469 if (currentLine > 0)
470 levelCurrent = styler.LevelAt(currentLine-1) >> 16;
471 int levelMinCurrent = levelCurrent;
472 int levelNext = levelCurrent;
473 char chNext = styler[startPos];
474 int styleNext = styler.StyleAt(startPos);
475 int style = initStyle;
476 for (Sci_PositionU i = startPos; i < endPos; i++) {
477 char ch = chNext;
478 chNext = styler.SafeGetCharAt(i + 1);
479 style = styleNext;
480 styleNext = styler.StyleAt(i + 1);
481 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
482 if (style == SCE_VISUALPROLOG_OPERATOR) {
483 if (ch == '{') {
484 // Measure the minimum before a '{' to allow
485 // folding on "} else {"
486 if (levelMinCurrent > levelNext) {
487 levelMinCurrent = levelNext;
488 }
489 levelNext++;
490 } else if (ch == '}') {
491 levelNext--;
492 }
493 }
494 if (!IsASpace(ch))
495 visibleChars++;
496 if (atEOL || (i == endPos-1)) {
497 int levelUse = levelCurrent;
498 int lev = levelUse | levelNext << 16;
499 if (levelUse < levelNext)
500 lev |= SC_FOLDLEVELHEADERFLAG;
501 if (lev != styler.LevelAt(currentLine)) {
502 styler.SetLevel(currentLine, lev);
503 }
504 currentLine++;
505 levelCurrent = levelNext;
506 levelMinCurrent = levelCurrent;
507 if (atEOL && (i == static_cast<Sci_PositionU>(styler.Length()-1))) {
508 // There is an empty line at end of file so give it same level and empty
509 styler.SetLevel(currentLine, (levelCurrent | levelCurrent << 16) | SC_FOLDLEVELWHITEFLAG);
510 }
511 visibleChars = 0;
512 }
513 }
514}
515
516LexerModule lmVisualProlog(SCLEX_VISUALPROLOG, LexerVisualProlog::LexerFactoryVisualProlog, "visualprolog", visualPrologWordLists);
517