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