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