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