1 // Scintilla source code edit control
2 /** @file LexTADS3.cxx
3  ** Lexer for TADS3.
4  **/
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
9  * be distributed.
10  */
11 
12 /*
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.
17  *
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.
21  *
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} ).
25  *
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.
30  *
31  * These embedded constructs influence the output and formatting and are an
32  * important part of a program and require highlighting.
33  *
34  * LINKS
35  * http://www.tads.org/
36  */
37 
38 #include <stdlib.h>
39 #include <string.h>
40 #include <ctype.h>
41 #include <stdio.h>
42 #include <stdarg.h>
43 
44 #include "Platform.h"
45 
46 #include "PropSet.h"
47 #include "Accessor.h"
48 #include "StyleContext.h"
49 #include "KeyWords.h"
50 #include "Scintilla.h"
51 #include "SciLexer.h"
52 
53 static const int T3_SINGLE_QUOTE = 1;
54 static const int T3_INT_EXPRESSION = 2;
55 
IsEOL(const int ch,const int chNext)56 static inline bool IsEOL(const int ch, const int chNext) {
57 	return (ch == '\r' && chNext != '\n') || (ch == '\n');
58 }
59 
IsASpaceOrTab(const int ch)60 static inline bool IsASpaceOrTab(const int ch) {
61 	return ch == ' ' || ch == '\t';
62 }
63 
IsATADS3Operator(const int ch)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 == '~';
70 }
71 
IsAWordChar(const int ch)72 static inline bool IsAWordChar(const int ch) {
73 	return isalnum(ch) || ch == '_' || ch == '.';
74 }
75 
IsAWordStart(const int ch)76 static inline bool IsAWordStart(const int ch) {
77 	return isalpha(ch) || ch == '_';
78 }
79 
IsAHexDigit(const int 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';
84 }
85 
IsAnHTMLChar(int ch)86 static inline bool IsAnHTMLChar(int ch) {
87 	return isalnum(ch) || ch == '-' || ch == '_' || ch == '.';
88 }
89 
IsADirectiveChar(int ch)90 static inline bool IsADirectiveChar(int ch) {
91 	return isalnum(ch) || isspace(ch) || ch == '-' || ch == '/';
92 }
93 
IsANumberStart(StyleContext & sc)94 static inline bool IsANumberStart(StyleContext &sc) {
95 	return isdigit(sc.ch)
96 		|| (!isdigit(sc.chPrev) && sc.ch == '.' && isdigit(sc.chNext));
97 }
98 
ColouriseTADS3Operator(StyleContext & sc)99 inline static void ColouriseTADS3Operator(StyleContext &sc) {
100 	int initState = sc.state;
101 	sc.SetState(SCE_T3_OPERATOR);
102 	sc.ForwardSetState(initState);
103 }
104 
ColouriseTADSHTMLString(StyleContext & sc,int & lineState)105 static void ColouriseTADSHTMLString(StyleContext &sc, int &lineState) {
106 	int endState = sc.state;
107 	int chQuote = sc.ch;
108 	if (endState == SCE_T3_HTML_STRING) {
109 		if (lineState&T3_SINGLE_QUOTE) {
110 			endState = SCE_T3_S_STRING;
111 			chQuote = '"';
112 		} else if (lineState&T3_INT_EXPRESSION) {
113 			endState = SCE_T3_X_STRING;
114 			chQuote = '\'';
115 		} else {
116 			endState = SCE_T3_D_STRING;
117 			chQuote = '\'';
118 		}
119 	} else {
120 		sc.SetState(SCE_T3_HTML_STRING);
121 		sc.Forward();
122 	}
123 	int chString = chQuote == '"'? '\'': '"';
124 
125 	while (sc.More()) {
126 		if (IsEOL(sc.ch, sc.chNext)) {
127 			return;
128 		}
129 		if (sc.ch == chQuote) {
130 			sc.ForwardSetState(endState);
131 			return;
132 		}
133 		if (sc.ch == chString) {
134 			sc.SetState(endState);
135 			return;
136 		}
137 		if (sc.Match('\\', static_cast<char>(chQuote))
138 			|| sc.Match('\\', static_cast<char>(chString))) {
139 			sc.Forward(2);
140 		} else {
141 			sc.Forward();
142 		}
143 	}
144 }
145 
ColouriseTADS3HTMLTagStart(StyleContext & sc)146 static void ColouriseTADS3HTMLTagStart(StyleContext &sc) {
147 	sc.SetState(SCE_T3_HTML_TAG);
148 	sc.Forward();
149 	if (sc.ch == '/') {
150 		sc.Forward();
151 	}
152 	while (IsAnHTMLChar(sc.ch)) {
153 		sc.Forward();
154 	}
155 }
156 
ColouriseTADS3HTMLTag(StyleContext & sc,int & lineState)157 static void ColouriseTADS3HTMLTag(StyleContext &sc, int &lineState) {
158 	int endState = sc.state;
159 	int chQuote = '"';
160 	int chString = '\'';
161 	switch (endState) {
162 		case SCE_T3_S_STRING:
163 			ColouriseTADS3HTMLTagStart(sc);
164 			sc.SetState(SCE_T3_HTML_DEFAULT);
165 			chQuote = '\'';
166 			chString = '"';
167 			break;
168 		case SCE_T3_D_STRING:
169 		case SCE_T3_X_STRING:
170 			ColouriseTADS3HTMLTagStart(sc);
171 			sc.SetState(SCE_T3_HTML_DEFAULT);
172 			break;
173 		case SCE_T3_HTML_DEFAULT:
174 			if (lineState&T3_SINGLE_QUOTE) {
175 				endState = SCE_T3_S_STRING;
176 				chQuote = '\'';
177 				chString = '"';
178 			} else if (lineState&T3_INT_EXPRESSION) {
179 				endState = SCE_T3_X_STRING;
180 			} else {
181 				endState = SCE_T3_D_STRING;
182 			}
183 			break;
184 	}
185 
186 	while (sc.More()) {
187 		if (IsEOL(sc.ch, sc.chNext)) {
188 			return;
189 		}
190 		if (sc.Match('/', '>')) {
191 			sc.SetState(SCE_T3_HTML_TAG);
192 			sc.Forward(2);
193 			sc.SetState(endState);
194 			return;
195 		}
196 		if (sc.ch == '>') {
197 			sc.SetState(SCE_T3_HTML_TAG);
198 			sc.ForwardSetState(endState);
199 			return;
200 		}
201 		if (sc.ch == chQuote) {
202 			sc.SetState(endState);
203 			return;
204 		}
205 		if (sc.ch == chString) {
206 			ColouriseTADSHTMLString(sc, lineState);
207 		} else if (sc.ch == '=') {
208 			ColouriseTADS3Operator(sc);
209 		} else {
210 			sc.Forward();
211 		}
212 	}
213 }
214 
ColouriseTADS3Keyword(StyleContext & sc,WordList * keywordlists[],unsigned int endPos)215 static void ColouriseTADS3Keyword(StyleContext &sc,
216 							WordList *keywordlists[], 	unsigned int endPos) {
217 	char s[250];
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))) {
225 		sc.Forward();
226 	}
227 	sc.GetCurrent(s, sizeof(s));
228 	if ( strcmp(s, "is") == 0 || strcmp(s, "not") == 0) {
229 		// have to find if "in" is next
230 		int n = 1;
231 		while (n + sc.currentPos < endPos && IsASpaceOrTab(sc.GetRelative(n)))
232 			n++;
233 		if (sc.GetRelative(n) == 'i' && sc.GetRelative(n+1) == 'n') {
234 			sc.Forward(n+2);
235 			sc.ChangeState(SCE_T3_KEYWORD);
236 		}
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);
245 	}
246 	sc.SetState(initState);
247 }
248 
ColouriseTADS3MsgParam(StyleContext & sc,int & lineState)249 static void ColouriseTADS3MsgParam(StyleContext &sc, int &lineState) {
250 	int endState = sc.state;
251 	int chQuote = '"';
252 	switch (endState) {
253 		case SCE_T3_S_STRING:
254 			sc.SetState(SCE_T3_MSG_PARAM);
255 			sc.Forward();
256 			chQuote = '\'';
257 			break;
258 		case SCE_T3_D_STRING:
259 		case SCE_T3_X_STRING:
260 			sc.SetState(SCE_T3_MSG_PARAM);
261 			sc.Forward();
262 			break;
263 		case SCE_T3_MSG_PARAM:
264 			if (lineState&T3_SINGLE_QUOTE) {
265 				endState = SCE_T3_S_STRING;
266 				chQuote = '\'';
267 			} else if (lineState&T3_INT_EXPRESSION) {
268 				endState = SCE_T3_X_STRING;
269 			} else {
270 				endState = SCE_T3_D_STRING;
271 			}
272 			break;
273 	}
274 	while (sc.More() && sc.ch != '}' && sc.ch != chQuote) {
275 		if (IsEOL(sc.ch, sc.chNext)) {
276 			return;
277 		}
278 		if (sc.ch == '\\') {
279 			sc.Forward();
280 		}
281 		sc.Forward();
282 	}
283 	if (sc.ch == chQuote) {
284 		sc.SetState(endState);
285 	} else {
286 		sc.ForwardSetState(endState);
287 	}
288 }
289 
ColouriseTADS3LibDirective(StyleContext & sc,int & lineState)290 static void ColouriseTADS3LibDirective(StyleContext &sc, int &lineState) {
291 	int initState = sc.state;
292 	int chQuote = '"';
293 	switch (initState) {
294 		case SCE_T3_S_STRING:
295 			sc.SetState(SCE_T3_LIB_DIRECTIVE);
296 			sc.Forward(2);
297 			chQuote = '\'';
298 			break;
299 		case SCE_T3_D_STRING:
300 			sc.SetState(SCE_T3_LIB_DIRECTIVE);
301 			sc.Forward(2);
302 			break;
303 		case SCE_T3_LIB_DIRECTIVE:
304 			if (lineState&T3_SINGLE_QUOTE) {
305 				initState = SCE_T3_S_STRING;
306 				chQuote = '\'';
307 			} else {
308 				initState = SCE_T3_D_STRING;
309 			}
310 			break;
311 	}
312 	while (sc.More() && IsADirectiveChar(sc.ch)) {
313 		if (IsEOL(sc.ch, sc.chNext)) {
314 			return;
315 		}
316 		sc.Forward();
317 	};
318 	if (sc.ch == '>' || !sc.More()) {
319 		sc.ForwardSetState(initState);
320 	} else if (sc.ch == chQuote) {
321 		sc.SetState(initState);
322 	} else {
323 		sc.ChangeState(initState);
324 		sc.Forward();
325 	}
326 }
327 
ColouriseTADS3String(StyleContext & sc,int & lineState)328 static void ColouriseTADS3String(StyleContext &sc, int &lineState) {
329 	int chQuote = sc.ch;
330 	int endState = sc.state;
331 	switch (sc.state) {
332 		case SCE_T3_DEFAULT:
333 		case SCE_T3_X_DEFAULT:
334 			if (chQuote == '"') {
335 				if (sc.state == SCE_T3_DEFAULT) {
336 					sc.SetState(SCE_T3_D_STRING);
337 				} else {
338 					sc.SetState(SCE_T3_X_STRING);
339 				}
340 				lineState &= ~T3_SINGLE_QUOTE;
341 			} else {
342 				sc.SetState(SCE_T3_S_STRING);
343 				lineState |= T3_SINGLE_QUOTE;
344 			}
345 			sc.Forward();
346 			break;
347 		case SCE_T3_S_STRING:
348 			chQuote = '\'';
349 			endState = lineState&T3_INT_EXPRESSION ?
350 				SCE_T3_X_DEFAULT : SCE_T3_DEFAULT;
351 			break;
352 		case SCE_T3_D_STRING:
353 			chQuote = '"';
354 			endState = SCE_T3_DEFAULT;
355 			break;
356 		case SCE_T3_X_STRING:
357 			chQuote = '"';
358 			endState = SCE_T3_X_DEFAULT;
359 			break;
360 	}
361 	while (sc.More()) {
362 		if (IsEOL(sc.ch, sc.chNext)) {
363 			return;
364 		}
365 		if (sc.ch == chQuote) {
366 			sc.ForwardSetState(endState);
367 			return;
368 		}
369 		if (sc.state == SCE_T3_D_STRING && sc.Match('<', '<')) {
370 			lineState |= T3_INT_EXPRESSION;
371 			sc.SetState(SCE_T3_X_DEFAULT);
372 			sc.Forward(2);
373 			return;
374 		}
375 		if (sc.Match('\\', static_cast<char>(chQuote))) {
376 			sc.Forward(2);
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);
383 		} else {
384 			sc.Forward();
385 		}
386 	}
387 }
388 
ColouriseTADS3Comment(StyleContext & sc,int endState)389 static void ColouriseTADS3Comment(StyleContext &sc, int endState) {
390 	sc.SetState(SCE_T3_BLOCK_COMMENT);
391 	while (sc.More()) {
392 		if (IsEOL(sc.ch, sc.chNext)) {
393 			return;
394 		}
395 		if (sc.Match('*', '/')) {
396 			sc.Forward(2);
397 			sc.SetState(endState);
398 			return;
399 		}
400 		sc.Forward();
401 	}
402 }
403 
ColouriseToEndOfLine(StyleContext & sc,int initState,int endState)404 static void ColouriseToEndOfLine(StyleContext &sc, int initState, int endState) {
405 	sc.SetState(initState);
406 	while (sc.More()) {
407 		if (sc.ch == '\\') {
408 			sc.Forward();
409 			if (IsEOL(sc.ch, sc.chNext)) {
410 					return;
411 			}
412 		}
413 		if (IsEOL(sc.ch, sc.chNext)) {
414 			sc.SetState(endState);
415 			return;
416 		}
417 		sc.Forward();
418 	}
419 }
420 
ColouriseTADS3Number(StyleContext & sc)421 static void ColouriseTADS3Number(StyleContext &sc) {
422 	int endState = sc.state;
423 	bool inHexNumber = false;
424 	bool seenE = false;
425 	bool seenDot = sc.ch == '.';
426 	sc.SetState(SCE_T3_NUMBER);
427 	if (sc.More()) {
428 		sc.Forward();
429 	}
430 	if (sc.chPrev == '0' && tolower(sc.ch) == 'x') {
431 		inHexNumber = true;
432 		sc.Forward();
433 	}
434 	while (sc.More()) {
435 		if (inHexNumber) {
436 			if (!IsAHexDigit(sc.ch)) {
437 				break;
438 			}
439 		} else if (!isdigit(sc.ch)) {
440 			if (!seenE && tolower(sc.ch) == 'e') {
441 				seenE = true;
442 				seenDot = true;
443 				if (sc.chNext == '+' || sc.chNext == '-') {
444 					sc.Forward();
445 				}
446 			} else if (!seenDot && sc.ch == '.') {
447 				seenDot = true;
448 			} else {
449 				break;
450 			}
451 		}
452 		sc.Forward();
453 	}
454 	sc.SetState(endState);
455 }
456 
ColouriseTADS3Doc(unsigned int startPos,int length,int initStyle,WordList * keywordlists[],Accessor & styler)457 static void ColouriseTADS3Doc(unsigned int startPos, int length, int initStyle,
458 							   WordList *keywordlists[], Accessor &styler) {
459 	int visibleChars = 0;
460 	int bracketLevel = 0;
461 	int lineState = 0;
462 	unsigned int endPos = startPos + length;
463 	int lineCurrent = styler.GetLine(startPos);
464 	if (lineCurrent > 0) {
465 		lineState = styler.GetLineState(lineCurrent-1);
466 	}
467 	StyleContext sc(startPos, length, initStyle, styler);
468 
469 	while (sc.More()) {
470 
471 		if (IsEOL(sc.ch, sc.chNext)) {
472 			styler.SetLineState(lineCurrent, lineState);
473 			lineCurrent++;
474 			visibleChars = 0;
475 			sc.Forward();
476 			if (sc.ch == '\n') {
477 				sc.Forward();
478 			}
479 		}
480 
481 		switch(sc.state) {
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);
486 				break;
487 			case SCE_T3_S_STRING:
488 			case SCE_T3_D_STRING:
489 			case SCE_T3_X_STRING:
490 				ColouriseTADS3String(sc, lineState);
491 				visibleChars++;
492 				break;
493 			case SCE_T3_MSG_PARAM:
494 				ColouriseTADS3MsgParam(sc, lineState);
495 				break;
496 			case SCE_T3_LIB_DIRECTIVE:
497 				ColouriseTADS3LibDirective(sc, lineState);
498 				break;
499 			case SCE_T3_HTML_DEFAULT:
500 				ColouriseTADS3HTMLTag(sc, lineState);
501 				break;
502 			case SCE_T3_HTML_STRING:
503 				ColouriseTADSHTMLString(sc, lineState);
504 				break;
505 			case SCE_T3_BLOCK_COMMENT:
506 				ColouriseTADS3Comment(sc, lineState&T3_INT_EXPRESSION ?
507 					SCE_T3_X_DEFAULT : SCE_T3_DEFAULT);
508 				break;
509 			case SCE_T3_DEFAULT:
510 			case SCE_T3_X_DEFAULT:
511 				if (IsASpaceOrTab(sc.ch)) {
512 					sc.Forward();
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);
517 					visibleChars++;
518 				} else if (sc.Match('/', '/')) {
519 					ColouriseToEndOfLine(sc, SCE_T3_LINE_COMMENT, sc.state);
520 				} else if (sc.ch == '"') {
521 					bracketLevel = 0;
522 					ColouriseTADS3String(sc, lineState);
523 					visibleChars++;
524 				} else if (sc.ch == '\'') {
525 					ColouriseTADS3String(sc, lineState);
526 					visibleChars++;
527 				} else if (sc.state == SCE_T3_X_DEFAULT && bracketLevel == 0
528 						   && sc.Match('>', '>')) {
529 					sc.Forward(2);
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) {
534 						if (sc.ch == '(') {
535 							bracketLevel++;
536 						} else if (sc.ch == ')') {
537 							bracketLevel--;
538 						}
539 					}
540 					ColouriseTADS3Operator(sc);
541 					visibleChars++;
542 				} else if (IsANumberStart(sc)) {
543 					ColouriseTADS3Number(sc);
544 					visibleChars++;
545 				} else if (IsAWordStart(sc.ch)) {
546 					ColouriseTADS3Keyword(sc, keywordlists, endPos);
547 					visibleChars++;
548 				} else if (sc.Match("...")) {
549 					sc.SetState(SCE_T3_IDENTIFIER);
550 					sc.Forward(3);
551 					sc.SetState(SCE_T3_DEFAULT);
552 				} else {
553 					sc.Forward();
554 					visibleChars++;
555 				}
556 				break;
557 			default:
558 				sc.SetState(SCE_T3_DEFAULT);
559 				sc.Forward();
560 		}
561 	}
562 	sc.Complete();
563 }
564 
565 /*
566  TADS3 has two styles of top level block (TLB). Eg
567 
568  // default style
569  silverKey : Key 'small silver key' 'small silver key'
570 	"A small key glints in the sunlight. "
571  ;
572 
573  and
574 
575  silverKey : Key {
576 	'small silver key'
577 	'small silver key'
578 	"A small key glints in the sunlight. "
579  }
580 
581  Some constructs mandate one or the other, but usually the author has may choose
582  either.
583 
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.
586 
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.
594 
595  expectingIdentifier == false && expectingIdentifier == false
596  Before the start of a TLB.
597 
598  expectingIdentifier == true && expectingIdentifier == true
599  Currently in an identifier.  Will accept identifier or punctuation.
600 
601  expectingIdentifier == true && expectingIdentifier == false
602  Just seen a punctuation character & now waiting for an identifier to start.
603 
604  expectingIdentifier == false && expectingIdentifier == truee
605  We were in an identifier and have seen space.  Now waiting to see a punctuation
606  character
607 
608  Space, comments & preprocessor directives are always acceptable and are
609  equivalent.
610 */
611 
612 static const int T3_SEENSTART = 1 << 12;
613 static const int T3_EXPECTINGIDENTIFIER = 1 << 13;
614 static const int T3_EXPECTINGPUNCTUATION = 1 << 14;
615 
IsStringTransition(int s1,int s2)616 static inline bool IsStringTransition(int s1, int s2) {
617 	return s1 != 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;
624 }
625 
IsATADS3Punctuation(const int ch)626 static inline bool IsATADS3Punctuation(const int ch) {
627 	return ch == ':' || ch == ',' || ch == '(' || ch == ')';
628 }
629 
IsAnIdentifier(const int style)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;
635 }
636 
IsSpaceEquivalent(const int ch,const int style)637 static inline bool IsSpaceEquivalent(const int ch, const int style) {
638 	return isspace(ch)
639 		|| style == SCE_T3_BLOCK_COMMENT
640 		|| style == SCE_T3_LINE_COMMENT
641 		|| style == SCE_T3_PREPROCESSOR;
642 }
643 
peekAhead(unsigned int startPos,unsigned int endPos,Accessor & styler)644 static char peekAhead(unsigned int startPos, unsigned int endPos,
645 					  Accessor &styler) {
646 	for (unsigned int i = startPos; i < endPos; i++) {
647 		int style = styler.StyleAt(i);
648 		char ch = styler[i];
649 		if (!IsSpaceEquivalent(ch, style)) {
650 			if (IsAnIdentifier(style)) {
651 				return 'a';
652 			}
653 			if (IsATADS3Punctuation(ch)) {
654 				return ':';
655 			}
656 			if (ch == '{') {
657 				return '{';
658 			}
659 			return '*';
660 		}
661 	}
662 	return ' ';
663 }
664 
FoldTADS3Doc(unsigned int startPos,int length,int initStyle,WordList * [],Accessor & styler)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;
670 	if (lineCurrent > 0)
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;
681 	char ch = chNext;
682 	int stylePrev = style;
683 	bool redo = false;
684 	for (unsigned int i = startPos; i < endPos; i++) {
685 		if (redo) {
686 			redo = false;
687 			i--;
688 		} else {
689 			ch = chNext;
690 			chNext = styler.SafeGetCharAt(i + 1);
691 			stylePrev = style;
692 			style = styleNext;
693 			styleNext = styler.StyleAt(i + 1);
694 		}
695 		bool atEOL = IsEOL(ch, chNext);
696 
697 		if (levelNext == SC_FOLDLEVELBASE) {
698 			if (IsSpaceEquivalent(ch, style)) {
699 				if (expectingPunctuation) {
700 					expectingIdentifier = 0;
701 				}
702 				if (style == SCE_T3_BLOCK_COMMENT) {
703 					levelNext++;
704 				}
705 			} else if (ch == '{') {
706 				levelNext++;
707 				seenStart = 0;
708 			} else if (ch == '\'' || ch == '"' || ch == '[') {
709 				levelNext++;
710 				if (seenStart) {
711 					redo = true;
712 				}
713 			} else if (ch == ';') {
714 				seenStart = 0;
715 				expectingIdentifier = 0;
716 				expectingPunctuation = 0;
717 			} else if (expectingIdentifier && expectingPunctuation) {
718 				if (IsATADS3Punctuation(ch)) {
719 					if (ch == ')' && peekAhead(i+1, endPos, styler) != '{') {
720 						levelNext++;
721 					} else {
722 						expectingPunctuation = 0;
723 					}
724 				} else if (!IsAnIdentifier(style)) {
725 					levelNext++;
726 				}
727 			} else if (expectingIdentifier && !expectingPunctuation) {
728 				if (!IsAnIdentifier(style)) {
729 					levelNext++;
730 				} else {
731 					expectingPunctuation = T3_EXPECTINGPUNCTUATION;
732 				}
733 			} else if (!expectingIdentifier && expectingPunctuation) {
734 				if (!IsATADS3Punctuation(ch)) {
735 					levelNext++;
736 				} else {
737 					if (ch == ')' && peekAhead(i+1, endPos, styler) != '{') {
738 						levelNext++;
739 					} else {
740 						expectingIdentifier = T3_EXPECTINGIDENTIFIER;
741 						expectingPunctuation = 0;
742 					}
743 				}
744 			} else if (!expectingIdentifier && !expectingPunctuation) {
745 				if (IsAnIdentifier(style)) {
746 					seenStart = T3_SEENSTART;
747 					expectingIdentifier = T3_EXPECTINGIDENTIFIER;
748 					expectingPunctuation = T3_EXPECTINGPUNCTUATION;
749 				}
750 			}
751 
752 			if (levelNext != SC_FOLDLEVELBASE && style != SCE_T3_BLOCK_COMMENT) {
753 				expectingIdentifier = 0;
754 				expectingPunctuation = 0;
755 			}
756 
757 		} else if (levelNext == SC_FOLDLEVELBASE+1 && seenStart
758 				   && ch == ';' && style == SCE_T3_OPERATOR ) {
759 			levelNext--;
760 			seenStart = 0;
761 		} else if (style == SCE_T3_BLOCK_COMMENT) {
762 			if (stylePrev != SCE_T3_BLOCK_COMMENT) {
763 				levelNext++;
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.
766 				levelNext--;
767 			}
768 		} else if (ch == '\'' || ch == '"') {
769 			if (IsStringTransition(style, stylePrev)) {
770 				if (levelMinCurrent > levelNext) {
771 					levelMinCurrent = levelNext;
772 				}
773 				levelNext++;
774 			} else if (IsStringTransition(style, styleNext)) {
775 				levelNext--;
776 			}
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;
783 				}
784 				levelNext++;
785 			} else if (ch == '}' || ch == ']') {
786 				levelNext--;
787 			}
788 		}
789 
790 		if (atEOL) {
791 			if (seenStart && levelNext == SC_FOLDLEVELBASE) {
792 				switch (peekAhead(i+1, endPos, styler)) {
793 					case ' ':
794 					case '{':
795 						break;
796 					case '*':
797 						levelNext++;
798 						break;
799 					case 'a':
800 						if (expectingPunctuation) {
801 							levelNext++;
802 						}
803 						break;
804 					case ':':
805 						if (expectingIdentifier) {
806 							levelNext++;
807 						}
808 						break;
809 				}
810 				if (levelNext != SC_FOLDLEVELBASE) {
811 					expectingIdentifier = 0;
812 					expectingPunctuation = 0;
813 				}
814 			}
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);
821 			}
822 			lineCurrent++;
823 			levelCurrent = levelNext;
824 			levelMinCurrent = levelCurrent;
825 		}
826 	}
827 }
828 
829 static const char * const tads3WordList[] = {
830 	"TADS3 Keywords",
831 	"User defined 1",
832 	"User defined 2",
833 	"User defined 3",
834 	0
835 };
836 
837 LexerModule lmTADS3(SCLEX_TADS3, ColouriseTADS3Doc, "tads3", FoldTADS3Doc, tads3WordList);
838