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