1
// Scintilla source code edit control
5
/* Copyright 2005 by Michael Cartmell
6
* Parts copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
7
* In particular FoldTADS3Doc is derived from FoldCppDoc
8
* The License.txt file describes the conditions under which this software may
13
* TADS3 is a language designed by Michael J. Roberts for the writing of text
14
* based games. TADS comes from Text Adventure Development System. It has good
15
* support for the processing and outputting of formatted text and much of a
16
* TADS program listing consists of strings.
18
* TADS has two types of strings, those enclosed in single quotes (') and those
19
* enclosed in double quotes ("). These strings have different symantics and
20
* can be given different highlighting if desired.
22
* There can be embedded within both types of strings html tags
23
* ( <tag key=value> ), library directives ( <.directive> ), and message
24
* parameters ( {The doctor's/his} ).
26
* Double quoted strings can also contain interpolated expressions
27
* ( << rug.moved ? ' and a hole in the floor. ' : nil >> ). These expressions
28
* may themselves contain single or double quoted strings, although the double
29
* quoted strings may not contain interpolated expressions.
31
* These embedded constructs influence the output and formatting and are an
32
* important part of a program and require highlighting.
35
* http://www.tads.org/
48
#include "StyleContext.h"
50
#include "Scintilla.h"
53
static const int T3_SINGLE_QUOTE = 1;
54
static const int T3_INT_EXPRESSION = 2;
56
static inline bool IsEOL(const int ch, const int chNext) {
57
return (ch == '\r' && chNext != '\n') || (ch == '\n');
60
static inline bool IsASpaceOrTab(const int ch) {
61
return ch == ' ' || ch == '\t';
64
static inline bool IsATADS3Operator(const int ch) {
65
return ch == '=' || ch == '{' || ch == '}' || ch == '(' || ch == ')'
66
|| ch == '[' || ch == ']' || ch == ',' || ch == ':' || ch == ';'
67
|| ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '%'
68
|| ch == '?' || ch == '!' || ch == '<' || ch == '>' || ch == '|'
69
|| ch == '@' || ch == '&' || ch == '~';
72
static inline bool IsAWordChar(const int ch) {
73
return isalnum(ch) || ch == '_' || ch == '.';
76
static inline bool IsAWordStart(const int ch) {
77
return isalpha(ch) || ch == '_';
80
static inline bool IsAHexDigit(const int ch) {
81
int lch = tolower(ch);
82
return isdigit(lch) || lch == 'a' || lch == 'b' || lch == 'c'
83
|| lch == 'd' || lch == 'e' || lch == 'f';
86
static inline bool IsAnHTMLChar(int ch) {
87
return isalnum(ch) || ch == '-' || ch == '_' || ch == '.';
90
static inline bool IsADirectiveChar(int ch) {
91
return isalnum(ch) || isspace(ch) || ch == '-' || ch == '/';
94
static inline bool IsANumberStart(StyleContext &sc) {
96
|| (!isdigit(sc.chPrev) && sc.ch == '.' && isdigit(sc.chNext));
99
inline static void ColouriseTADS3Operator(StyleContext &sc) {
100
int initState = sc.state;
101
sc.SetState(SCE_T3_OPERATOR);
102
sc.ForwardSetState(initState);
105
static void ColouriseTADSHTMLString(StyleContext &sc, int &lineState) {
106
int endState = sc.state;
108
if (endState == SCE_T3_HTML_STRING) {
109
if (lineState&T3_SINGLE_QUOTE) {
110
endState = SCE_T3_S_STRING;
112
} else if (lineState&T3_INT_EXPRESSION) {
113
endState = SCE_T3_X_STRING;
116
endState = SCE_T3_D_STRING;
120
sc.SetState(SCE_T3_HTML_STRING);
123
int chString = chQuote == '"'? '\'': '"';
126
if (IsEOL(sc.ch, sc.chNext)) {
129
if (sc.ch == chQuote) {
130
sc.ForwardSetState(endState);
133
if (sc.ch == chString) {
134
sc.SetState(endState);
137
if (sc.Match('\\', static_cast<char>(chQuote))
138
|| sc.Match('\\', static_cast<char>(chString))) {
146
static void ColouriseTADS3HTMLTagStart(StyleContext &sc) {
147
sc.SetState(SCE_T3_HTML_TAG);
152
while (IsAnHTMLChar(sc.ch)) {
157
static void ColouriseTADS3HTMLTag(StyleContext &sc, int &lineState) {
158
int endState = sc.state;
162
case SCE_T3_S_STRING:
163
ColouriseTADS3HTMLTagStart(sc);
164
sc.SetState(SCE_T3_HTML_DEFAULT);
168
case SCE_T3_D_STRING:
169
case SCE_T3_X_STRING:
170
ColouriseTADS3HTMLTagStart(sc);
171
sc.SetState(SCE_T3_HTML_DEFAULT);
173
case SCE_T3_HTML_DEFAULT:
174
if (lineState&T3_SINGLE_QUOTE) {
175
endState = SCE_T3_S_STRING;
178
} else if (lineState&T3_INT_EXPRESSION) {
179
endState = SCE_T3_X_STRING;
181
endState = SCE_T3_D_STRING;
187
if (IsEOL(sc.ch, sc.chNext)) {
190
if (sc.Match('/', '>')) {
191
sc.SetState(SCE_T3_HTML_TAG);
193
sc.SetState(endState);
197
sc.SetState(SCE_T3_HTML_TAG);
198
sc.ForwardSetState(endState);
201
if (sc.ch == chQuote) {
202
sc.SetState(endState);
205
if (sc.ch == chString) {
206
ColouriseTADSHTMLString(sc, lineState);
207
} else if (sc.ch == '=') {
208
ColouriseTADS3Operator(sc);
215
static void ColouriseTADS3Keyword(StyleContext &sc,
216
WordList *keywordlists[], unsigned int endPos) {
218
WordList &keywords = *keywordlists[0];
219
WordList &userwords1 = *keywordlists[1];
220
WordList &userwords2 = *keywordlists[2];
221
WordList &userwords3 = *keywordlists[3];
222
int initState = sc.state;
223
sc.SetState(SCE_T3_IDENTIFIER);
224
while (sc.More() && (IsAWordChar(sc.ch))) {
227
sc.GetCurrent(s, sizeof(s));
228
if ( strcmp(s, "is") == 0 || strcmp(s, "not") == 0) {
229
// have to find if "in" is next
231
while (n + sc.currentPos < endPos && IsASpaceOrTab(sc.GetRelative(n)))
233
if (sc.GetRelative(n) == 'i' && sc.GetRelative(n+1) == 'n') {
235
sc.ChangeState(SCE_T3_KEYWORD);
237
} else if (keywords.InList(s)) {
238
sc.ChangeState(SCE_T3_KEYWORD);
239
} else if (userwords3.InList(s)) {
240
sc.ChangeState(SCE_T3_USER3);
241
} else if (userwords2.InList(s)) {
242
sc.ChangeState(SCE_T3_USER2);
243
} else if (userwords1.InList(s)) {
244
sc.ChangeState(SCE_T3_USER1);
246
sc.SetState(initState);
249
static void ColouriseTADS3MsgParam(StyleContext &sc, int &lineState) {
250
int endState = sc.state;
253
case SCE_T3_S_STRING:
254
sc.SetState(SCE_T3_MSG_PARAM);
258
case SCE_T3_D_STRING:
259
case SCE_T3_X_STRING:
260
sc.SetState(SCE_T3_MSG_PARAM);
263
case SCE_T3_MSG_PARAM:
264
if (lineState&T3_SINGLE_QUOTE) {
265
endState = SCE_T3_S_STRING;
267
} else if (lineState&T3_INT_EXPRESSION) {
268
endState = SCE_T3_X_STRING;
270
endState = SCE_T3_D_STRING;
274
while (sc.More() && sc.ch != '}' && sc.ch != chQuote) {
275
if (IsEOL(sc.ch, sc.chNext)) {
283
if (sc.ch == chQuote) {
284
sc.SetState(endState);
286
sc.ForwardSetState(endState);
290
static void ColouriseTADS3LibDirective(StyleContext &sc, int &lineState) {
291
int initState = sc.state;
294
case SCE_T3_S_STRING:
295
sc.SetState(SCE_T3_LIB_DIRECTIVE);
299
case SCE_T3_D_STRING:
300
sc.SetState(SCE_T3_LIB_DIRECTIVE);
303
case SCE_T3_LIB_DIRECTIVE:
304
if (lineState&T3_SINGLE_QUOTE) {
305
initState = SCE_T3_S_STRING;
308
initState = SCE_T3_D_STRING;
312
while (sc.More() && IsADirectiveChar(sc.ch)) {
313
if (IsEOL(sc.ch, sc.chNext)) {
318
if (sc.ch == '>' || !sc.More()) {
319
sc.ForwardSetState(initState);
320
} else if (sc.ch == chQuote) {
321
sc.SetState(initState);
323
sc.ChangeState(initState);
328
static void ColouriseTADS3String(StyleContext &sc, int &lineState) {
330
int endState = sc.state;
333
case SCE_T3_X_DEFAULT:
334
if (chQuote == '"') {
335
if (sc.state == SCE_T3_DEFAULT) {
336
sc.SetState(SCE_T3_D_STRING);
338
sc.SetState(SCE_T3_X_STRING);
340
lineState &= ~T3_SINGLE_QUOTE;
342
sc.SetState(SCE_T3_S_STRING);
343
lineState |= T3_SINGLE_QUOTE;
347
case SCE_T3_S_STRING:
349
endState = lineState&T3_INT_EXPRESSION ?
350
SCE_T3_X_DEFAULT : SCE_T3_DEFAULT;
352
case SCE_T3_D_STRING:
354
endState = SCE_T3_DEFAULT;
356
case SCE_T3_X_STRING:
358
endState = SCE_T3_X_DEFAULT;
362
if (IsEOL(sc.ch, sc.chNext)) {
365
if (sc.ch == chQuote) {
366
sc.ForwardSetState(endState);
369
if (sc.state == SCE_T3_D_STRING && sc.Match('<', '<')) {
370
lineState |= T3_INT_EXPRESSION;
371
sc.SetState(SCE_T3_X_DEFAULT);
375
if (sc.Match('\\', static_cast<char>(chQuote))) {
377
} else if (sc.ch == '{') {
378
ColouriseTADS3MsgParam(sc, lineState);
379
} else if (sc.Match('<', '.')) {
380
ColouriseTADS3LibDirective(sc, lineState);
381
} else if (sc.ch == '<') {
382
ColouriseTADS3HTMLTag(sc, lineState);
389
static void ColouriseTADS3Comment(StyleContext &sc, int endState) {
390
sc.SetState(SCE_T3_BLOCK_COMMENT);
392
if (IsEOL(sc.ch, sc.chNext)) {
395
if (sc.Match('*', '/')) {
397
sc.SetState(endState);
404
static void ColouriseToEndOfLine(StyleContext &sc, int initState, int endState) {
405
sc.SetState(initState);
409
if (IsEOL(sc.ch, sc.chNext)) {
413
if (IsEOL(sc.ch, sc.chNext)) {
414
sc.SetState(endState);
421
static void ColouriseTADS3Number(StyleContext &sc) {
422
int endState = sc.state;
423
bool inHexNumber = false;
425
bool seenDot = sc.ch == '.';
426
sc.SetState(SCE_T3_NUMBER);
430
if (sc.chPrev == '0' && tolower(sc.ch) == 'x') {
436
if (!IsAHexDigit(sc.ch)) {
439
} else if (!isdigit(sc.ch)) {
440
if (!seenE && tolower(sc.ch) == 'e') {
443
if (sc.chNext == '+' || sc.chNext == '-') {
446
} else if (!seenDot && sc.ch == '.') {
454
sc.SetState(endState);
457
static void ColouriseTADS3Doc(unsigned int startPos, int length, int initStyle,
458
WordList *keywordlists[], Accessor &styler) {
459
int visibleChars = 0;
460
int bracketLevel = 0;
462
unsigned int endPos = startPos + length;
463
int lineCurrent = styler.GetLine(startPos);
464
if (lineCurrent > 0) {
465
lineState = styler.GetLineState(lineCurrent-1);
467
StyleContext sc(startPos, length, initStyle, styler);
471
if (IsEOL(sc.ch, sc.chNext)) {
472
styler.SetLineState(lineCurrent, lineState);
482
case SCE_T3_PREPROCESSOR:
483
case SCE_T3_LINE_COMMENT:
484
ColouriseToEndOfLine(sc, sc.state, lineState&T3_INT_EXPRESSION ?
485
SCE_T3_X_DEFAULT : SCE_T3_DEFAULT);
487
case SCE_T3_S_STRING:
488
case SCE_T3_D_STRING:
489
case SCE_T3_X_STRING:
490
ColouriseTADS3String(sc, lineState);
493
case SCE_T3_MSG_PARAM:
494
ColouriseTADS3MsgParam(sc, lineState);
496
case SCE_T3_LIB_DIRECTIVE:
497
ColouriseTADS3LibDirective(sc, lineState);
499
case SCE_T3_HTML_DEFAULT:
500
ColouriseTADS3HTMLTag(sc, lineState);
502
case SCE_T3_HTML_STRING:
503
ColouriseTADSHTMLString(sc, lineState);
505
case SCE_T3_BLOCK_COMMENT:
506
ColouriseTADS3Comment(sc, lineState&T3_INT_EXPRESSION ?
507
SCE_T3_X_DEFAULT : SCE_T3_DEFAULT);
510
case SCE_T3_X_DEFAULT:
511
if (IsASpaceOrTab(sc.ch)) {
513
} else if (sc.ch == '#' && visibleChars == 0) {
514
ColouriseToEndOfLine(sc, SCE_T3_PREPROCESSOR, sc.state);
515
} else if (sc.Match('/', '*')) {
516
ColouriseTADS3Comment(sc, sc.state);
518
} else if (sc.Match('/', '/')) {
519
ColouriseToEndOfLine(sc, SCE_T3_LINE_COMMENT, sc.state);
520
} else if (sc.ch == '"') {
522
ColouriseTADS3String(sc, lineState);
524
} else if (sc.ch == '\'') {
525
ColouriseTADS3String(sc, lineState);
527
} else if (sc.state == SCE_T3_X_DEFAULT && bracketLevel == 0
528
&& sc.Match('>', '>')) {
530
sc.SetState(SCE_T3_D_STRING);
531
lineState &= ~(T3_SINGLE_QUOTE|T3_INT_EXPRESSION);
532
} else if (IsATADS3Operator(sc.ch)) {
533
if (sc.state == SCE_T3_X_DEFAULT) {
536
} else if (sc.ch == ')') {
540
ColouriseTADS3Operator(sc);
542
} else if (IsANumberStart(sc)) {
543
ColouriseTADS3Number(sc);
545
} else if (IsAWordStart(sc.ch)) {
546
ColouriseTADS3Keyword(sc, keywordlists, endPos);
548
} else if (sc.Match("...")) {
549
sc.SetState(SCE_T3_IDENTIFIER);
551
sc.SetState(SCE_T3_DEFAULT);
558
sc.SetState(SCE_T3_DEFAULT);
566
TADS3 has two styles of top level block (TLB). Eg
569
silverKey : Key 'small silver key' 'small silver key'
570
"A small key glints in the sunlight. "
578
"A small key glints in the sunlight. "
581
Some constructs mandate one or the other, but usually the author has may choose
584
T3_SEENSTART is used to indicate that a braceless TLB has been (potentially)
585
seen and is also used to match the closing ';' of the default style.
587
T3_EXPECTINGIDENTIFIER and T3_EXPECTINGPUNCTUATION are used to keep track of
588
what characters may be seen without incrementing the block level. The general
589
pattern is identifier <punc> identifier, acceptable punctuation characters
590
are ':', ',', '(' and ')'. No attempt is made to ensure that punctuation
591
characters are syntactically correct, eg parentheses match. A ')' always
592
signifies the start of a block. We just need to check if it is followed by a
593
'{', in which case we let the brace handling code handle the folding level.
595
expectingIdentifier == false && expectingIdentifier == false
596
Before the start of a TLB.
598
expectingIdentifier == true && expectingIdentifier == true
599
Currently in an identifier. Will accept identifier or punctuation.
601
expectingIdentifier == true && expectingIdentifier == false
602
Just seen a punctuation character & now waiting for an identifier to start.
604
expectingIdentifier == false && expectingIdentifier == truee
605
We were in an identifier and have seen space. Now waiting to see a punctuation
608
Space, comments & preprocessor directives are always acceptable and are
612
static const int T3_SEENSTART = 1 << 12;
613
static const int T3_EXPECTINGIDENTIFIER = 1 << 13;
614
static const int T3_EXPECTINGPUNCTUATION = 1 << 14;
616
static inline bool IsStringTransition(int s1, int s2) {
618
&& (s1 == SCE_T3_S_STRING || s1 == SCE_T3_X_STRING
619
|| s1 == SCE_T3_D_STRING && s2 != SCE_T3_X_DEFAULT)
620
&& s2 != SCE_T3_LIB_DIRECTIVE
621
&& s2 != SCE_T3_MSG_PARAM
622
&& s2 != SCE_T3_HTML_TAG
623
&& s2 != SCE_T3_HTML_STRING;
626
static inline bool IsATADS3Punctuation(const int ch) {
627
return ch == ':' || ch == ',' || ch == '(' || ch == ')';
630
static inline bool IsAnIdentifier(const int style) {
631
return style == SCE_T3_IDENTIFIER
632
|| style == SCE_T3_USER1
633
|| style == SCE_T3_USER2
634
|| style == SCE_T3_USER3;
637
static inline bool IsSpaceEquivalent(const int ch, const int style) {
639
|| style == SCE_T3_BLOCK_COMMENT
640
|| style == SCE_T3_LINE_COMMENT
641
|| style == SCE_T3_PREPROCESSOR;
644
static char peekAhead(unsigned int startPos, unsigned int endPos,
646
for (unsigned int i = startPos; i < endPos; i++) {
647
int style = styler.StyleAt(i);
649
if (!IsSpaceEquivalent(ch, style)) {
650
if (IsAnIdentifier(style)) {
653
if (IsATADS3Punctuation(ch)) {
665
static void FoldTADS3Doc(unsigned int startPos, int length, int initStyle,
666
WordList *[], Accessor &styler) {
667
unsigned int endPos = startPos + length;
668
int lineCurrent = styler.GetLine(startPos);
669
int levelCurrent = SC_FOLDLEVELBASE;
671
levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
672
int seenStart = levelCurrent & T3_SEENSTART;
673
int expectingIdentifier = levelCurrent & T3_EXPECTINGIDENTIFIER;
674
int expectingPunctuation = levelCurrent & T3_EXPECTINGPUNCTUATION;
675
levelCurrent &= SC_FOLDLEVELNUMBERMASK;
676
int levelMinCurrent = levelCurrent;
677
int levelNext = levelCurrent;
678
char chNext = styler[startPos];
679
int styleNext = styler.StyleAt(startPos);
680
int style = initStyle;
682
int stylePrev = style;
684
for (unsigned int i = startPos; i < endPos; i++) {
690
chNext = styler.SafeGetCharAt(i + 1);
693
styleNext = styler.StyleAt(i + 1);
695
bool atEOL = IsEOL(ch, chNext);
697
if (levelNext == SC_FOLDLEVELBASE) {
698
if (IsSpaceEquivalent(ch, style)) {
699
if (expectingPunctuation) {
700
expectingIdentifier = 0;
702
if (style == SCE_T3_BLOCK_COMMENT) {
705
} else if (ch == '{') {
708
} else if (ch == '\'' || ch == '"' || ch == '[') {
713
} else if (ch == ';') {
715
expectingIdentifier = 0;
716
expectingPunctuation = 0;
717
} else if (expectingIdentifier && expectingPunctuation) {
718
if (IsATADS3Punctuation(ch)) {
719
if (ch == ')' && peekAhead(i+1, endPos, styler) != '{') {
722
expectingPunctuation = 0;
724
} else if (!IsAnIdentifier(style)) {
727
} else if (expectingIdentifier && !expectingPunctuation) {
728
if (!IsAnIdentifier(style)) {
731
expectingPunctuation = T3_EXPECTINGPUNCTUATION;
733
} else if (!expectingIdentifier && expectingPunctuation) {
734
if (!IsATADS3Punctuation(ch)) {
737
if (ch == ')' && peekAhead(i+1, endPos, styler) != '{') {
740
expectingIdentifier = T3_EXPECTINGIDENTIFIER;
741
expectingPunctuation = 0;
744
} else if (!expectingIdentifier && !expectingPunctuation) {
745
if (IsAnIdentifier(style)) {
746
seenStart = T3_SEENSTART;
747
expectingIdentifier = T3_EXPECTINGIDENTIFIER;
748
expectingPunctuation = T3_EXPECTINGPUNCTUATION;
752
if (levelNext != SC_FOLDLEVELBASE && style != SCE_T3_BLOCK_COMMENT) {
753
expectingIdentifier = 0;
754
expectingPunctuation = 0;
757
} else if (levelNext == SC_FOLDLEVELBASE+1 && seenStart
758
&& ch == ';' && style == SCE_T3_OPERATOR ) {
761
} else if (style == SCE_T3_BLOCK_COMMENT) {
762
if (stylePrev != SCE_T3_BLOCK_COMMENT) {
764
} else if (styleNext != SCE_T3_BLOCK_COMMENT && !atEOL) {
765
// Comments don't end at end of line and the next character may be unstyled.
768
} else if (ch == '\'' || ch == '"') {
769
if (IsStringTransition(style, stylePrev)) {
770
if (levelMinCurrent > levelNext) {
771
levelMinCurrent = levelNext;
774
} else if (IsStringTransition(style, styleNext)) {
777
} else if (style == SCE_T3_OPERATOR) {
778
if (ch == '{' || ch == '[') {
779
// Measure the minimum before a '{' to allow
780
// folding on "} else {"
781
if (levelMinCurrent > levelNext) {
782
levelMinCurrent = levelNext;
785
} else if (ch == '}' || ch == ']') {
791
if (seenStart && levelNext == SC_FOLDLEVELBASE) {
792
switch (peekAhead(i+1, endPos, styler)) {
800
if (expectingPunctuation) {
805
if (expectingIdentifier) {
810
if (levelNext != SC_FOLDLEVELBASE) {
811
expectingIdentifier = 0;
812
expectingPunctuation = 0;
815
int lev = levelMinCurrent | (levelNext | expectingIdentifier
816
| expectingPunctuation | seenStart) << 16;
817
if (levelMinCurrent < levelNext)
818
lev |= SC_FOLDLEVELHEADERFLAG;
819
if (lev != styler.LevelAt(lineCurrent)) {
820
styler.SetLevel(lineCurrent, lev);
823
levelCurrent = levelNext;
824
levelMinCurrent = levelCurrent;
829
static const char * const tads3WordList[] = {
837
LexerModule lmTADS3(SCLEX_TADS3, ColouriseTADS3Doc, "tads3", FoldTADS3Doc, tads3WordList);