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