1 // Scintilla source code edit control
2 /** @file LexTADS3.cxx
3  ** Lexer for TADS3.
4  **/
5 // Copyright 1998-2006 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 /*
9  * TADS3 is a language designed by Michael J. Roberts for the writing of text
10  * based games.  TADS comes from Text Adventure Development System.  It has good
11  * support for the processing and outputting of formatted text and much of a
12  * TADS program listing consists of strings.
13  *
14  * TADS has two types of strings, those enclosed in single quotes (') and those
15  * enclosed in double quotes (").  These strings have different symantics and
16  * can be given different highlighting if desired.
17  *
18  * There can be embedded within both types of strings html tags
19  * ( <tag key=value> ), library directives ( <.directive> ), and message
20  * parameters ( {The doctor's/his} ).
21  *
22  * Double quoted strings can also contain interpolated expressions
23  * ( << rug.moved ? ' and a hole in the floor. ' : nil >> ).  These expressions
24  * may themselves contain single or double quoted strings, although the double
25  * quoted strings may not contain interpolated expressions.
26  *
27  * These embedded constructs influence the output and formatting and are an
28  * important part of a program and require highlighting.
29  *
30  * LINKS
31  * http://www.tads.org/
32  */
33 
34 #include <stdlib.h>
35 #include <string.h>
36 #include <stdio.h>
37 #include <stdarg.h>
38 #include <assert.h>
39 #include <ctype.h>
40 
41 #include "ILexer.h"
42 #include "Scintilla.h"
43 #include "SciLexer.h"
44 
45 #include "WordList.h"
46 #include "LexAccessor.h"
47 #include "Accessor.h"
48 #include "StyleContext.h"
49 #include "CharacterSet.h"
50 #include "LexerModule.h"
51 
52 using namespace Scintilla;
53 
54 static const int T3_SINGLE_QUOTE = 1;
55 static const int T3_INT_EXPRESSION = 2;
56 static const int T3_INT_EXPRESSION_IN_TAG = 4;
57 static const int T3_HTML_SQUOTE = 8;
58 
IsEOL(const int ch,const int chNext)59 static inline bool IsEOL(const int ch, const int chNext) {
60         return (ch == '\r' && chNext != '\n') || (ch == '\n');
61 }
62 
63 /*
64  *   Test the current character to see if it's the START of an EOL sequence;
65  *   if so, skip ahead to the last character of the sequence and return true,
66  *   and if not just return false.  There are a few places where we want to
67  *   check to see if a newline sequence occurs at a particular point, but
68  *   where a caller expects a subroutine to stop only upon reaching the END
69  *   of a newline sequence (in particular, CR-LF on Windows).  That's why
70  *   IsEOL() above only returns true on CR if the CR isn't followed by an LF
71  *   - it doesn't want to admit that there's a newline until reaching the END
72  *   of the sequence.  We meet both needs by saying that there's a newline
73  *   when we see the CR in a CR-LF, but skipping the CR before returning so
74  *   that the caller's caller will see that we've stopped at the LF.
75  */
IsEOLSkip(StyleContext & sc)76 static inline bool IsEOLSkip(StyleContext &sc)
77 {
78     /* test for CR-LF */
79     if (sc.ch == '\r' && sc.chNext == '\n')
80     {
81         /* got CR-LF - skip the CR and indicate that we're at a newline */
82         sc.Forward();
83         return true;
84     }
85 
86     /*
87      *   in other cases, we have at most a 1-character newline, so do the
88      *   normal IsEOL test
89      */
90     return IsEOL(sc.ch, sc.chNext);
91 }
92 
IsATADS3Operator(const int ch)93 static inline bool IsATADS3Operator(const int ch) {
94         return ch == '=' || ch == '{' || ch == '}' || ch == '(' || ch == ')'
95                 || ch == '[' || ch == ']' || ch == ',' || ch == ':' || ch == ';'
96                 || ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '%'
97                 || ch == '?' || ch == '!' || ch == '<' || ch == '>' || ch == '|'
98                 || ch == '@' || ch == '&' || ch == '~';
99 }
100 
IsAWordChar(const int ch)101 static inline bool IsAWordChar(const int ch) {
102         return isalnum(ch) || ch == '_';
103 }
104 
IsAWordStart(const int ch)105 static inline bool IsAWordStart(const int ch) {
106         return isalpha(ch) || ch == '_';
107 }
108 
IsAHexDigit(const int ch)109 static inline bool IsAHexDigit(const int ch) {
110         int lch = tolower(ch);
111         return isdigit(lch) || lch == 'a' || lch == 'b' || lch == 'c'
112                 || lch == 'd' || lch == 'e' || lch == 'f';
113 }
114 
IsAnHTMLChar(int ch)115 static inline bool IsAnHTMLChar(int ch) {
116         return isalnum(ch) || ch == '-' || ch == '_' || ch == '.';
117 }
118 
IsADirectiveChar(int ch)119 static inline bool IsADirectiveChar(int ch) {
120         return isalnum(ch) || isspace(ch) || ch == '-' || ch == '/';
121 }
122 
IsANumberStart(StyleContext & sc)123 static inline bool IsANumberStart(StyleContext &sc) {
124         return isdigit(sc.ch)
125                 || (!isdigit(sc.chPrev) && sc.ch == '.' && isdigit(sc.chNext));
126 }
127 
ColouriseTADS3Operator(StyleContext & sc)128 inline static void ColouriseTADS3Operator(StyleContext &sc) {
129         int initState = sc.state;
130         int c = sc.ch;
131         sc.SetState(c == '{' || c == '}' ? SCE_T3_BRACE : SCE_T3_OPERATOR);
132         sc.ForwardSetState(initState);
133 }
134 
ColouriseTADSHTMLString(StyleContext & sc,int & lineState)135 static void ColouriseTADSHTMLString(StyleContext &sc, int &lineState) {
136         int endState = sc.state;
137         int chQuote = sc.ch;
138         int chString = (lineState & T3_SINGLE_QUOTE) ? '\'' : '"';
139         if (endState == SCE_T3_HTML_STRING) {
140                 if (lineState&T3_SINGLE_QUOTE) {
141                         endState = SCE_T3_S_STRING;
142                         chString = '\'';
143                 } else if (lineState&T3_INT_EXPRESSION) {
144                         endState = SCE_T3_X_STRING;
145                         chString = '"';
146                 } else {
147                         endState = SCE_T3_HTML_DEFAULT;
148                         chString = '"';
149                 }
150                 chQuote = (lineState & T3_HTML_SQUOTE) ? '\'' : '"';
151         } else {
152                 sc.SetState(SCE_T3_HTML_STRING);
153                 sc.Forward();
154         }
155         if (chQuote == '"')
156                 lineState &= ~T3_HTML_SQUOTE;
157         else
158                 lineState |= T3_HTML_SQUOTE;
159 
160         while (sc.More()) {
161                 if (IsEOL(sc.ch, sc.chNext)) {
162                         return;
163                 }
164                 if (sc.ch == chQuote) {
165                         sc.ForwardSetState(endState);
166                         return;
167                 }
168                 if (sc.Match('\\', static_cast<char>(chQuote))) {
169                         sc.Forward(2);
170                         sc.SetState(endState);
171                         return;
172                 }
173                 if (sc.ch == chString) {
174                         sc.SetState(SCE_T3_DEFAULT);
175                         return;
176                 }
177 
178                 if (sc.Match('<', '<')) {
179                         lineState |= T3_INT_EXPRESSION | T3_INT_EXPRESSION_IN_TAG;
180                         sc.SetState(SCE_T3_X_DEFAULT);
181                         sc.Forward(2);
182                         return;
183                 }
184 
185                 if (sc.Match('\\', static_cast<char>(chQuote))
186                         || sc.Match('\\', static_cast<char>(chString))
187                         || sc.Match('\\', '\\')) {
188                         sc.Forward(2);
189                 } else {
190                         sc.Forward();
191                 }
192         }
193 }
194 
ColouriseTADS3HTMLTagStart(StyleContext & sc)195 static void ColouriseTADS3HTMLTagStart(StyleContext &sc) {
196         sc.SetState(SCE_T3_HTML_TAG);
197         sc.Forward();
198         if (sc.ch == '/') {
199                 sc.Forward();
200         }
201         while (IsAnHTMLChar(sc.ch)) {
202                 sc.Forward();
203         }
204 }
205 
ColouriseTADS3HTMLTag(StyleContext & sc,int & lineState)206 static void ColouriseTADS3HTMLTag(StyleContext &sc, int &lineState) {
207         int endState = sc.state;
208         int chQuote = '"';
209         int chString = '\'';
210         switch (endState) {
211                 case SCE_T3_S_STRING:
212                         ColouriseTADS3HTMLTagStart(sc);
213                         sc.SetState(SCE_T3_HTML_DEFAULT);
214                         chQuote = '\'';
215                         chString = '"';
216                         break;
217                 case SCE_T3_D_STRING:
218                 case SCE_T3_X_STRING:
219                         ColouriseTADS3HTMLTagStart(sc);
220                         sc.SetState(SCE_T3_HTML_DEFAULT);
221                         break;
222                 case SCE_T3_HTML_DEFAULT:
223                         if (lineState&T3_SINGLE_QUOTE) {
224                                 endState = SCE_T3_S_STRING;
225                                 chQuote = '\'';
226                                 chString = '"';
227                         } else if (lineState&T3_INT_EXPRESSION) {
228                                 endState = SCE_T3_X_STRING;
229                         } else {
230                                 endState = SCE_T3_D_STRING;
231                         }
232                         break;
233         }
234 
235         while (sc.More()) {
236                 if (IsEOL(sc.ch, sc.chNext)) {
237                         return;
238                 }
239                 if (sc.Match('/', '>')) {
240                         sc.SetState(SCE_T3_HTML_TAG);
241                         sc.Forward(2);
242                         sc.SetState(endState);
243                         return;
244                 }
245                 if (sc.ch == '>') {
246                         sc.SetState(SCE_T3_HTML_TAG);
247                         sc.ForwardSetState(endState);
248                         return;
249                 }
250                 if (sc.ch == chQuote) {
251                         sc.SetState(endState);
252                         return;
253                 }
254                 if (sc.Match('\\', static_cast<char>(chQuote))) {
255                         sc.Forward();
256                         ColouriseTADSHTMLString(sc, lineState);
257                         if (sc.state == SCE_T3_X_DEFAULT)
258                             break;
259                 } else if (sc.ch == chString) {
260                         ColouriseTADSHTMLString(sc, lineState);
261                 } else if (sc.ch == '=') {
262                         ColouriseTADS3Operator(sc);
263                 } else {
264                         sc.Forward();
265                 }
266         }
267 }
268 
ColouriseTADS3Keyword(StyleContext & sc,WordList * keywordlists[],Sci_PositionU endPos)269 static void ColouriseTADS3Keyword(StyleContext &sc,
270                                                         WordList *keywordlists[],       Sci_PositionU endPos) {
271         char s[250];
272         WordList &keywords = *keywordlists[0];
273         WordList &userwords1 = *keywordlists[1];
274         WordList &userwords2 = *keywordlists[2];
275         WordList &userwords3 = *keywordlists[3];
276         int initState = sc.state;
277         sc.SetState(SCE_T3_IDENTIFIER);
278         while (sc.More() && (IsAWordChar(sc.ch))) {
279                 sc.Forward();
280         }
281         sc.GetCurrent(s, sizeof(s));
282         if ( strcmp(s, "is") == 0 || strcmp(s, "not") == 0) {
283                 // have to find if "in" is next
284                 Sci_Position n = 1;
285                 while (n + sc.currentPos < endPos && IsASpaceOrTab(sc.GetRelative(n)))
286                         n++;
287                 if (sc.GetRelative(n) == 'i' && sc.GetRelative(n+1) == 'n') {
288                         sc.Forward(n+2);
289                         sc.ChangeState(SCE_T3_KEYWORD);
290                 }
291         } else if (keywords.InList(s)) {
292                 sc.ChangeState(SCE_T3_KEYWORD);
293         } else if (userwords3.InList(s)) {
294                 sc.ChangeState(SCE_T3_USER3);
295         } else if (userwords2.InList(s)) {
296                 sc.ChangeState(SCE_T3_USER2);
297         } else if (userwords1.InList(s)) {
298                 sc.ChangeState(SCE_T3_USER1);
299         }
300         sc.SetState(initState);
301 }
302 
ColouriseTADS3MsgParam(StyleContext & sc,int & lineState)303 static void ColouriseTADS3MsgParam(StyleContext &sc, int &lineState) {
304         int endState = sc.state;
305         int chQuote = '"';
306         switch (endState) {
307                 case SCE_T3_S_STRING:
308                         sc.SetState(SCE_T3_MSG_PARAM);
309                         sc.Forward();
310                         chQuote = '\'';
311                         break;
312                 case SCE_T3_D_STRING:
313                 case SCE_T3_X_STRING:
314                         sc.SetState(SCE_T3_MSG_PARAM);
315                         sc.Forward();
316                         break;
317                 case SCE_T3_MSG_PARAM:
318                         if (lineState&T3_SINGLE_QUOTE) {
319                                 endState = SCE_T3_S_STRING;
320                                 chQuote = '\'';
321                         } else if (lineState&T3_INT_EXPRESSION) {
322                                 endState = SCE_T3_X_STRING;
323                         } else {
324                                 endState = SCE_T3_D_STRING;
325                         }
326                         break;
327         }
328         while (sc.More() && sc.ch != '}' && sc.ch != chQuote) {
329                 if (IsEOL(sc.ch, sc.chNext)) {
330                         return;
331                 }
332                 if (sc.ch == '\\') {
333                         sc.Forward();
334                 }
335                 sc.Forward();
336         }
337         if (sc.ch == chQuote) {
338                 sc.SetState(endState);
339         } else {
340                 sc.ForwardSetState(endState);
341         }
342 }
343 
ColouriseTADS3LibDirective(StyleContext & sc,int & lineState)344 static void ColouriseTADS3LibDirective(StyleContext &sc, int &lineState) {
345         int initState = sc.state;
346         int chQuote = '"';
347         switch (initState) {
348                 case SCE_T3_S_STRING:
349                         sc.SetState(SCE_T3_LIB_DIRECTIVE);
350                         sc.Forward(2);
351                         chQuote = '\'';
352                         break;
353                 case SCE_T3_D_STRING:
354                         sc.SetState(SCE_T3_LIB_DIRECTIVE);
355                         sc.Forward(2);
356                         break;
357                 case SCE_T3_LIB_DIRECTIVE:
358                         if (lineState&T3_SINGLE_QUOTE) {
359                                 initState = SCE_T3_S_STRING;
360                                 chQuote = '\'';
361                         } else {
362                                 initState = SCE_T3_D_STRING;
363                         }
364                         break;
365         }
366         while (sc.More() && IsADirectiveChar(sc.ch)) {
367                 if (IsEOL(sc.ch, sc.chNext)) {
368                         return;
369                 }
370                 sc.Forward();
371         };
372         if (sc.ch == '>' || !sc.More()) {
373                 sc.ForwardSetState(initState);
374         } else if (sc.ch == chQuote) {
375                 sc.SetState(initState);
376         } else {
377                 sc.ChangeState(initState);
378                 sc.Forward();
379         }
380 }
381 
ColouriseTADS3String(StyleContext & sc,int & lineState)382 static void ColouriseTADS3String(StyleContext &sc, int &lineState) {
383         int chQuote = sc.ch;
384         int endState = sc.state;
385         switch (sc.state) {
386                 case SCE_T3_DEFAULT:
387                 case SCE_T3_X_DEFAULT:
388                         if (chQuote == '"') {
389                                 if (sc.state == SCE_T3_DEFAULT) {
390                                         sc.SetState(SCE_T3_D_STRING);
391                                 } else {
392                                         sc.SetState(SCE_T3_X_STRING);
393                                 }
394                                 lineState &= ~T3_SINGLE_QUOTE;
395                         } else {
396                                 sc.SetState(SCE_T3_S_STRING);
397                                 lineState |= T3_SINGLE_QUOTE;
398                         }
399                         sc.Forward();
400                         break;
401                 case SCE_T3_S_STRING:
402                         chQuote = '\'';
403                         endState = lineState&T3_INT_EXPRESSION ?
404                                 SCE_T3_X_DEFAULT : SCE_T3_DEFAULT;
405                         break;
406                 case SCE_T3_D_STRING:
407                         chQuote = '"';
408                         endState = SCE_T3_DEFAULT;
409                         break;
410                 case SCE_T3_X_STRING:
411                         chQuote = '"';
412                         endState = SCE_T3_X_DEFAULT;
413                         break;
414         }
415         while (sc.More()) {
416                 if (IsEOL(sc.ch, sc.chNext)) {
417                         return;
418                 }
419                 if (sc.ch == chQuote) {
420                         sc.ForwardSetState(endState);
421                         return;
422                 }
423                 if (sc.state == SCE_T3_D_STRING && sc.Match('<', '<')) {
424                         lineState |= T3_INT_EXPRESSION;
425                         sc.SetState(SCE_T3_X_DEFAULT);
426                         sc.Forward(2);
427                         return;
428                 }
429                 if (sc.Match('\\', static_cast<char>(chQuote))
430                     || sc.Match('\\', '\\')) {
431                         sc.Forward(2);
432                 } else if (sc.ch == '{') {
433                         ColouriseTADS3MsgParam(sc, lineState);
434                 } else if (sc.Match('<', '.')) {
435                         ColouriseTADS3LibDirective(sc, lineState);
436                 } else if (sc.ch == '<') {
437                         ColouriseTADS3HTMLTag(sc, lineState);
438                         if (sc.state == SCE_T3_X_DEFAULT)
439                                 return;
440                 } else {
441                         sc.Forward();
442                 }
443         }
444 }
445 
ColouriseTADS3Comment(StyleContext & sc,int endState)446 static void ColouriseTADS3Comment(StyleContext &sc, int endState) {
447         sc.SetState(SCE_T3_BLOCK_COMMENT);
448         while (sc.More()) {
449                 if (IsEOL(sc.ch, sc.chNext)) {
450                         return;
451                 }
452                 if (sc.Match('*', '/')) {
453                         sc.Forward(2);
454                         sc.SetState(endState);
455                         return;
456                 }
457                 sc.Forward();
458         }
459 }
460 
ColouriseToEndOfLine(StyleContext & sc,int initState,int endState)461 static void ColouriseToEndOfLine(StyleContext &sc, int initState, int endState) {
462         sc.SetState(initState);
463         while (sc.More()) {
464                 if (sc.ch == '\\') {
465                         sc.Forward();
466                         if (IsEOLSkip(sc)) {
467                                         return;
468                         }
469                 }
470                 if (IsEOL(sc.ch, sc.chNext)) {
471                         sc.SetState(endState);
472                         return;
473                 }
474                 sc.Forward();
475         }
476 }
477 
ColouriseTADS3Number(StyleContext & sc)478 static void ColouriseTADS3Number(StyleContext &sc) {
479         int endState = sc.state;
480         bool inHexNumber = false;
481         bool seenE = false;
482         bool seenDot = sc.ch == '.';
483         sc.SetState(SCE_T3_NUMBER);
484         if (sc.More()) {
485                 sc.Forward();
486         }
487         if (sc.chPrev == '0' && tolower(sc.ch) == 'x') {
488                 inHexNumber = true;
489                 sc.Forward();
490         }
491         while (sc.More()) {
492                 if (inHexNumber) {
493                         if (!IsAHexDigit(sc.ch)) {
494                                 break;
495                         }
496                 } else if (!isdigit(sc.ch)) {
497                         if (!seenE && tolower(sc.ch) == 'e') {
498                                 seenE = true;
499                                 seenDot = true;
500                                 if (sc.chNext == '+' || sc.chNext == '-') {
501                                         sc.Forward();
502                                 }
503                         } else if (!seenDot && sc.ch == '.') {
504                                 seenDot = true;
505                         } else {
506                                 break;
507                         }
508                 }
509                 sc.Forward();
510         }
511         sc.SetState(endState);
512 }
513 
ColouriseTADS3Doc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * keywordlists[],Accessor & styler)514 static void ColouriseTADS3Doc(Sci_PositionU startPos, Sci_Position length, int initStyle,
515                                                            WordList *keywordlists[], Accessor &styler) {
516         int visibleChars = 0;
517         int bracketLevel = 0;
518         int lineState = 0;
519         Sci_PositionU endPos = startPos + length;
520         Sci_Position lineCurrent = styler.GetLine(startPos);
521         if (lineCurrent > 0) {
522                 lineState = styler.GetLineState(lineCurrent-1);
523         }
524         StyleContext sc(startPos, length, initStyle, styler);
525 
526         while (sc.More()) {
527 
528                 if (IsEOL(sc.ch, sc.chNext)) {
529                         styler.SetLineState(lineCurrent, lineState);
530                         lineCurrent++;
531                         visibleChars = 0;
532                         sc.Forward();
533                         if (sc.ch == '\n') {
534                                 sc.Forward();
535                         }
536                 }
537 
538                 switch(sc.state) {
539                         case SCE_T3_PREPROCESSOR:
540                         case SCE_T3_LINE_COMMENT:
541                                 ColouriseToEndOfLine(sc, sc.state, lineState&T3_INT_EXPRESSION ?
542                                         SCE_T3_X_DEFAULT : SCE_T3_DEFAULT);
543                                 break;
544                         case SCE_T3_S_STRING:
545                         case SCE_T3_D_STRING:
546                         case SCE_T3_X_STRING:
547                                 ColouriseTADS3String(sc, lineState);
548                                 visibleChars++;
549                                 break;
550                         case SCE_T3_MSG_PARAM:
551                                 ColouriseTADS3MsgParam(sc, lineState);
552                                 break;
553                         case SCE_T3_LIB_DIRECTIVE:
554                                 ColouriseTADS3LibDirective(sc, lineState);
555                                 break;
556                         case SCE_T3_HTML_DEFAULT:
557                                 ColouriseTADS3HTMLTag(sc, lineState);
558                                 break;
559                         case SCE_T3_HTML_STRING:
560                                 ColouriseTADSHTMLString(sc, lineState);
561                                 break;
562                         case SCE_T3_BLOCK_COMMENT:
563                                 ColouriseTADS3Comment(sc, lineState&T3_INT_EXPRESSION ?
564                                         SCE_T3_X_DEFAULT : SCE_T3_DEFAULT);
565                                 break;
566                         case SCE_T3_DEFAULT:
567                         case SCE_T3_X_DEFAULT:
568                                 if (IsASpaceOrTab(sc.ch)) {
569                                         sc.Forward();
570                                 } else if (sc.ch == '#' && visibleChars == 0) {
571                                         ColouriseToEndOfLine(sc, SCE_T3_PREPROCESSOR, sc.state);
572                                 } else if (sc.Match('/', '*')) {
573                                         ColouriseTADS3Comment(sc, sc.state);
574                                         visibleChars++;
575                                 } else if (sc.Match('/', '/')) {
576                                         ColouriseToEndOfLine(sc, SCE_T3_LINE_COMMENT, sc.state);
577                                 } else if (sc.ch == '"') {
578                                         bracketLevel = 0;
579                                         ColouriseTADS3String(sc, lineState);
580                                         visibleChars++;
581                                 } else if (sc.ch == '\'') {
582                                         ColouriseTADS3String(sc, lineState);
583                                         visibleChars++;
584                                 } else if (sc.state == SCE_T3_X_DEFAULT && bracketLevel == 0
585                                                    && sc.Match('>', '>')) {
586                                         sc.Forward(2);
587                                         sc.SetState(SCE_T3_D_STRING);
588                                         if (lineState & T3_INT_EXPRESSION_IN_TAG)
589                                                 sc.SetState(SCE_T3_HTML_STRING);
590                                         lineState &= ~(T3_SINGLE_QUOTE|T3_INT_EXPRESSION
591                                                        |T3_INT_EXPRESSION_IN_TAG);
592                                 } else if (IsATADS3Operator(sc.ch)) {
593                                         if (sc.state == SCE_T3_X_DEFAULT) {
594                                                 if (sc.ch == '(') {
595                                                         bracketLevel++;
596                                                 } else if (sc.ch == ')' && bracketLevel > 0) {
597                                                         bracketLevel--;
598                                                 }
599                                         }
600                                         ColouriseTADS3Operator(sc);
601                                         visibleChars++;
602                                 } else if (IsANumberStart(sc)) {
603                                         ColouriseTADS3Number(sc);
604                                         visibleChars++;
605                                 } else if (IsAWordStart(sc.ch)) {
606                                         ColouriseTADS3Keyword(sc, keywordlists, endPos);
607                                         visibleChars++;
608                                 } else if (sc.Match("...")) {
609                                         sc.SetState(SCE_T3_IDENTIFIER);
610                                         sc.Forward(3);
611                                         sc.SetState(SCE_T3_DEFAULT);
612                                 } else {
613                                         sc.Forward();
614                                         visibleChars++;
615                                 }
616                                 break;
617                         default:
618                                 sc.SetState(SCE_T3_DEFAULT);
619                                 sc.Forward();
620                 }
621         }
622         sc.Complete();
623 }
624 
625 /*
626  TADS3 has two styles of top level block (TLB). Eg
627 
628  // default style
629  silverKey : Key 'small silver key' 'small silver key'
630         "A small key glints in the sunlight. "
631  ;
632 
633  and
634 
635  silverKey : Key {
636         'small silver key'
637         'small silver key'
638         "A small key glints in the sunlight. "
639  }
640 
641  Some constructs mandate one or the other, but usually the author has may choose
642  either.
643 
644  T3_SEENSTART is used to indicate that a braceless TLB has been (potentially)
645  seen and is also used to match the closing ';' of the default style.
646 
647  T3_EXPECTINGIDENTIFIER and T3_EXPECTINGPUNCTUATION are used to keep track of
648  what characters may be seen without incrementing the block level.  The general
649  pattern is identifier <punc> identifier, acceptable punctuation characters
650  are ':', ',', '(' and ')'.  No attempt is made to ensure that punctuation
651  characters are syntactically correct, eg parentheses match. A ')' always
652  signifies the start of a block.  We just need to check if it is followed by a
653  '{', in which case we let the brace handling code handle the folding level.
654 
655  expectingIdentifier == false && expectingIdentifier == false
656  Before the start of a TLB.
657 
658  expectingIdentifier == true && expectingIdentifier == true
659  Currently in an identifier.  Will accept identifier or punctuation.
660 
661  expectingIdentifier == true && expectingIdentifier == false
662  Just seen a punctuation character & now waiting for an identifier to start.
663 
664  expectingIdentifier == false && expectingIdentifier == truee
665  We were in an identifier and have seen space.  Now waiting to see a punctuation
666  character
667 
668  Space, comments & preprocessor directives are always acceptable and are
669  equivalent.
670 */
671 
672 static const int T3_SEENSTART = 1 << 12;
673 static const int T3_EXPECTINGIDENTIFIER = 1 << 13;
674 static const int T3_EXPECTINGPUNCTUATION = 1 << 14;
675 
IsStringTransition(int s1,int s2)676 static inline bool IsStringTransition(int s1, int s2) {
677         return s1 != s2
678                 && (s1 == SCE_T3_S_STRING || s1 == SCE_T3_X_STRING
679                         || (s1 == SCE_T3_D_STRING && s2 != SCE_T3_X_DEFAULT))
680                 && s2 != SCE_T3_LIB_DIRECTIVE
681                 && s2 != SCE_T3_MSG_PARAM
682                 && s2 != SCE_T3_HTML_TAG
683                 && s2 != SCE_T3_HTML_STRING;
684 }
685 
IsATADS3Punctuation(const int ch)686 static inline bool IsATADS3Punctuation(const int ch) {
687         return ch == ':' || ch == ',' || ch == '(' || ch == ')';
688 }
689 
IsAnIdentifier(const int style)690 static inline bool IsAnIdentifier(const int style) {
691         return style == SCE_T3_IDENTIFIER
692                 || style == SCE_T3_USER1
693                 || style == SCE_T3_USER2
694                 || style == SCE_T3_USER3;
695 }
696 
IsAnOperator(const int style)697 static inline bool IsAnOperator(const int style) {
698     return style == SCE_T3_OPERATOR || style == SCE_T3_BRACE;
699 }
700 
IsSpaceEquivalent(const int ch,const int style)701 static inline bool IsSpaceEquivalent(const int ch, const int style) {
702         return isspace(ch)
703                 || style == SCE_T3_BLOCK_COMMENT
704                 || style == SCE_T3_LINE_COMMENT
705                 || style == SCE_T3_PREPROCESSOR;
706 }
707 
peekAhead(Sci_PositionU startPos,Sci_PositionU endPos,Accessor & styler)708 static char peekAhead(Sci_PositionU startPos, Sci_PositionU endPos,
709                                           Accessor &styler) {
710         for (Sci_PositionU i = startPos; i < endPos; i++) {
711                 int style = styler.StyleAt(i);
712                 char ch = styler[i];
713                 if (!IsSpaceEquivalent(ch, style)) {
714                         if (IsAnIdentifier(style)) {
715                                 return 'a';
716                         }
717                         if (IsATADS3Punctuation(ch)) {
718                                 return ':';
719                         }
720                         if (ch == '{') {
721                                 return '{';
722                         }
723                         return '*';
724                 }
725         }
726         return ' ';
727 }
728 
FoldTADS3Doc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * [],Accessor & styler)729 static void FoldTADS3Doc(Sci_PositionU startPos, Sci_Position length, int initStyle,
730                             WordList *[], Accessor &styler) {
731         Sci_PositionU endPos = startPos + length;
732         Sci_Position lineCurrent = styler.GetLine(startPos);
733         int levelCurrent = SC_FOLDLEVELBASE;
734         if (lineCurrent > 0)
735                 levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
736         int seenStart = levelCurrent & T3_SEENSTART;
737         int expectingIdentifier = levelCurrent & T3_EXPECTINGIDENTIFIER;
738         int expectingPunctuation = levelCurrent & T3_EXPECTINGPUNCTUATION;
739         levelCurrent &= SC_FOLDLEVELNUMBERMASK;
740         int levelMinCurrent = levelCurrent;
741         int levelNext = levelCurrent;
742         char chNext = styler[startPos];
743         int styleNext = styler.StyleAt(startPos);
744         int style = initStyle;
745         char ch = chNext;
746         int stylePrev = style;
747         bool redo = false;
748         for (Sci_PositionU i = startPos; i < endPos; i++) {
749                 if (redo) {
750                         redo = false;
751                         i--;
752                 } else {
753                         ch = chNext;
754                         chNext = styler.SafeGetCharAt(i + 1);
755                         stylePrev = style;
756                         style = styleNext;
757                         styleNext = styler.StyleAt(i + 1);
758                 }
759                 bool atEOL = IsEOL(ch, chNext);
760 
761                 if (levelNext == SC_FOLDLEVELBASE) {
762                         if (IsSpaceEquivalent(ch, style)) {
763                                 if (expectingPunctuation) {
764                                         expectingIdentifier = 0;
765                                 }
766                                 if (style == SCE_T3_BLOCK_COMMENT) {
767                                         levelNext++;
768                                 }
769                         } else if (ch == '{') {
770                                 levelNext++;
771                                 seenStart = 0;
772                         } else if (ch == '\'' || ch == '"' || ch == '[') {
773                                 levelNext++;
774                                 if (seenStart) {
775                                         redo = true;
776                                 }
777                         } else if (ch == ';') {
778                                 seenStart = 0;
779                                 expectingIdentifier = 0;
780                                 expectingPunctuation = 0;
781                         } else if (expectingIdentifier && expectingPunctuation) {
782                                 if (IsATADS3Punctuation(ch)) {
783                                         if (ch == ')' && peekAhead(i+1, endPos, styler) != '{') {
784                                                 levelNext++;
785                                         } else {
786                                                 expectingPunctuation = 0;
787                                         }
788                                 } else if (!IsAnIdentifier(style)) {
789                                         levelNext++;
790                                 }
791                         } else if (expectingIdentifier && !expectingPunctuation) {
792                                 if (!IsAnIdentifier(style)) {
793                                         levelNext++;
794                                 } else {
795                                         expectingPunctuation = T3_EXPECTINGPUNCTUATION;
796                                 }
797                         } else if (!expectingIdentifier && expectingPunctuation) {
798                                 if (!IsATADS3Punctuation(ch)) {
799                                         levelNext++;
800                                 } else {
801                                         if (ch == ')' && peekAhead(i+1, endPos, styler) != '{') {
802                                                 levelNext++;
803                                         } else {
804                                                 expectingIdentifier = T3_EXPECTINGIDENTIFIER;
805                                                 expectingPunctuation = 0;
806                                         }
807                                 }
808                         } else if (!expectingIdentifier && !expectingPunctuation) {
809                                 if (IsAnIdentifier(style)) {
810                                         seenStart = T3_SEENSTART;
811                                         expectingIdentifier = T3_EXPECTINGIDENTIFIER;
812                                         expectingPunctuation = T3_EXPECTINGPUNCTUATION;
813                                 }
814                         }
815 
816                         if (levelNext != SC_FOLDLEVELBASE && style != SCE_T3_BLOCK_COMMENT) {
817                                 expectingIdentifier = 0;
818                                 expectingPunctuation = 0;
819                         }
820 
821                 } else if (levelNext == SC_FOLDLEVELBASE+1 && seenStart
822                                    && ch == ';' && IsAnOperator(style)) {
823                         levelNext--;
824                         seenStart = 0;
825                 } else if (style == SCE_T3_BLOCK_COMMENT) {
826                         if (stylePrev != SCE_T3_BLOCK_COMMENT) {
827                                 levelNext++;
828                         } else if (styleNext != SCE_T3_BLOCK_COMMENT && !atEOL) {
829                                 // Comments don't end at end of line and the next character may be unstyled.
830                                 levelNext--;
831                         }
832                 } else if (ch == '\'' || ch == '"') {
833                         if (IsStringTransition(style, stylePrev)) {
834                                 if (levelMinCurrent > levelNext) {
835                                         levelMinCurrent = levelNext;
836                                 }
837                                 levelNext++;
838                         } else if (IsStringTransition(style, styleNext)) {
839                                 levelNext--;
840                         }
841                 } else if (IsAnOperator(style)) {
842                         if (ch == '{' || ch == '[') {
843                                 // Measure the minimum before a '{' to allow
844                                 // folding on "} else {"
845                                 if (levelMinCurrent > levelNext) {
846                                         levelMinCurrent = levelNext;
847                                 }
848                                 levelNext++;
849                         } else if (ch == '}' || ch == ']') {
850                                 levelNext--;
851                         }
852                 }
853 
854                 if (atEOL) {
855                         if (seenStart && levelNext == SC_FOLDLEVELBASE) {
856                                 switch (peekAhead(i+1, endPos, styler)) {
857                                         case ' ':
858                                         case '{':
859                                                 break;
860                                         case '*':
861                                                 levelNext++;
862                                                 break;
863                                         case 'a':
864                                                 if (expectingPunctuation) {
865                                                         levelNext++;
866                                                 }
867                                                 break;
868                                         case ':':
869                                                 if (expectingIdentifier) {
870                                                         levelNext++;
871                                                 }
872                                                 break;
873                                 }
874                                 if (levelNext != SC_FOLDLEVELBASE) {
875                                         expectingIdentifier = 0;
876                                         expectingPunctuation = 0;
877                                 }
878                         }
879                         int lev = levelMinCurrent | (levelNext | expectingIdentifier
880                                 | expectingPunctuation | seenStart) << 16;
881                         if (levelMinCurrent < levelNext)
882                                 lev |= SC_FOLDLEVELHEADERFLAG;
883                         if (lev != styler.LevelAt(lineCurrent)) {
884                                 styler.SetLevel(lineCurrent, lev);
885                         }
886                         lineCurrent++;
887                         levelCurrent = levelNext;
888                         levelMinCurrent = levelCurrent;
889                 }
890         }
891 }
892 
893 static const char * const tads3WordList[] = {
894         "TADS3 Keywords",
895         "User defined 1",
896         "User defined 2",
897         "User defined 3",
898         0
899 };
900 
901 LexerModule lmTADS3(SCLEX_TADS3, ColouriseTADS3Doc, "tads3", FoldTADS3Doc, tads3WordList);
902