1 // Scintilla source code edit control
2 /** @file LexAda.cxx
3  ** Lexer for Ada 95
4  **/
5 // Copyright 2002 by Sergey Koshcheyev <sergey.k@seznam.cz>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 #include <stdlib.h>
9 #include <string.h>
10 #include <stdio.h>
11 #include <stdarg.h>
12 #include <assert.h>
13 
14 #include <string>
15 
16 #include "ILexer.h"
17 #include "Scintilla.h"
18 #include "SciLexer.h"
19 
20 #include "WordList.h"
21 #include "LexAccessor.h"
22 #include "Accessor.h"
23 #include "StyleContext.h"
24 #include "CharacterSet.h"
25 #include "LexerModule.h"
26 
27 #ifdef SCI_NAMESPACE
28 using namespace Scintilla;
29 #endif
30 
31 /*
32  * Interface
33  */
34 
35 static void ColouriseDocument(
36     unsigned int startPos,
37     int length,
38     int initStyle,
39     WordList *keywordlists[],
40     Accessor &styler);
41 
42 static const char * const adaWordListDesc[] = {
43 	"Keywords",
44 	0
45 };
46 
47 LexerModule lmAda(SCLEX_ADA, ColouriseDocument, "ada", NULL, adaWordListDesc);
48 
49 /*
50  * Implementation
51  */
52 
53 // Functions that have apostropheStartsAttribute as a parameter set it according to whether
54 // an apostrophe encountered after processing the current token will start an attribute or
55 // a character literal.
56 static void ColouriseCharacter(StyleContext& sc, bool& apostropheStartsAttribute);
57 static void ColouriseComment(StyleContext& sc, bool& apostropheStartsAttribute);
58 static void ColouriseContext(StyleContext& sc, char chEnd, int stateEOL);
59 static void ColouriseDelimiter(StyleContext& sc, bool& apostropheStartsAttribute);
60 static void ColouriseLabel(StyleContext& sc, WordList& keywords, bool& apostropheStartsAttribute);
61 static void ColouriseNumber(StyleContext& sc, bool& apostropheStartsAttribute);
62 static void ColouriseString(StyleContext& sc, bool& apostropheStartsAttribute);
63 static void ColouriseWhiteSpace(StyleContext& sc, bool& apostropheStartsAttribute);
64 static void ColouriseWord(StyleContext& sc, WordList& keywords, bool& apostropheStartsAttribute);
65 
66 static inline bool IsDelimiterCharacter(int ch);
67 static inline bool IsNumberStartCharacter(int ch);
68 static inline bool IsNumberCharacter(int ch);
69 static inline bool IsSeparatorOrDelimiterCharacter(int ch);
70 static bool IsValidIdentifier(const std::string& identifier);
71 static bool IsValidNumber(const std::string& number);
72 static inline bool IsWordStartCharacter(int ch);
73 static inline bool IsWordCharacter(int ch);
74 
ColouriseCharacter(StyleContext & sc,bool & apostropheStartsAttribute)75 static void ColouriseCharacter(StyleContext& sc, bool& apostropheStartsAttribute) {
76 	apostropheStartsAttribute = true;
77 
78 	sc.SetState(SCE_ADA_CHARACTER);
79 
80 	// Skip the apostrophe and one more character (so that '' is shown as non-terminated and '''
81 	// is handled correctly)
82 	sc.Forward();
83 	sc.Forward();
84 
85 	ColouriseContext(sc, '\'', SCE_ADA_CHARACTEREOL);
86 }
87 
ColouriseContext(StyleContext & sc,char chEnd,int stateEOL)88 static void ColouriseContext(StyleContext& sc, char chEnd, int stateEOL) {
89 	while (!sc.atLineEnd && !sc.Match(chEnd)) {
90 		sc.Forward();
91 	}
92 
93 	if (!sc.atLineEnd) {
94 		sc.ForwardSetState(SCE_ADA_DEFAULT);
95 	} else {
96 		sc.ChangeState(stateEOL);
97 	}
98 }
99 
ColouriseComment(StyleContext & sc,bool &)100 static void ColouriseComment(StyleContext& sc, bool& /*apostropheStartsAttribute*/) {
101 	// Apostrophe meaning is not changed, but the parameter is present for uniformity
102 
103 	sc.SetState(SCE_ADA_COMMENTLINE);
104 
105 	while (!sc.atLineEnd) {
106 		sc.Forward();
107 	}
108 }
109 
ColouriseDelimiter(StyleContext & sc,bool & apostropheStartsAttribute)110 static void ColouriseDelimiter(StyleContext& sc, bool& apostropheStartsAttribute) {
111 	apostropheStartsAttribute = sc.Match (')');
112 	sc.SetState(SCE_ADA_DELIMITER);
113 	sc.ForwardSetState(SCE_ADA_DEFAULT);
114 }
115 
ColouriseLabel(StyleContext & sc,WordList & keywords,bool & apostropheStartsAttribute)116 static void ColouriseLabel(StyleContext& sc, WordList& keywords, bool& apostropheStartsAttribute) {
117 	apostropheStartsAttribute = false;
118 
119 	sc.SetState(SCE_ADA_LABEL);
120 
121 	// Skip "<<"
122 	sc.Forward();
123 	sc.Forward();
124 
125 	std::string identifier;
126 
127 	while (!sc.atLineEnd && !IsSeparatorOrDelimiterCharacter(sc.ch)) {
128 		identifier += static_cast<char>(tolower(sc.ch));
129 		sc.Forward();
130 	}
131 
132 	// Skip ">>"
133 	if (sc.Match('>', '>')) {
134 		sc.Forward();
135 		sc.Forward();
136 	} else {
137 		sc.ChangeState(SCE_ADA_ILLEGAL);
138 	}
139 
140 	// If the name is an invalid identifier or a keyword, then make it invalid label
141 	if (!IsValidIdentifier(identifier) || keywords.InList(identifier.c_str())) {
142 		sc.ChangeState(SCE_ADA_ILLEGAL);
143 	}
144 
145 	sc.SetState(SCE_ADA_DEFAULT);
146 
147 }
148 
ColouriseNumber(StyleContext & sc,bool & apostropheStartsAttribute)149 static void ColouriseNumber(StyleContext& sc, bool& apostropheStartsAttribute) {
150 	apostropheStartsAttribute = true;
151 
152 	std::string number;
153 	sc.SetState(SCE_ADA_NUMBER);
154 
155 	// Get all characters up to a delimiter or a separator, including points, but excluding
156 	// double points (ranges).
157 	while (!IsSeparatorOrDelimiterCharacter(sc.ch) || (sc.ch == '.' && sc.chNext != '.')) {
158 		number += static_cast<char>(sc.ch);
159 		sc.Forward();
160 	}
161 
162 	// Special case: exponent with sign
163 	if ((sc.chPrev == 'e' || sc.chPrev == 'E') &&
164 	        (sc.ch == '+' || sc.ch == '-')) {
165 		number += static_cast<char>(sc.ch);
166 		sc.Forward ();
167 
168 		while (!IsSeparatorOrDelimiterCharacter(sc.ch)) {
169 			number += static_cast<char>(sc.ch);
170 			sc.Forward();
171 		}
172 	}
173 
174 	if (!IsValidNumber(number)) {
175 		sc.ChangeState(SCE_ADA_ILLEGAL);
176 	}
177 
178 	sc.SetState(SCE_ADA_DEFAULT);
179 }
180 
ColouriseString(StyleContext & sc,bool & apostropheStartsAttribute)181 static void ColouriseString(StyleContext& sc, bool& apostropheStartsAttribute) {
182 	apostropheStartsAttribute = true;
183 
184 	sc.SetState(SCE_ADA_STRING);
185 	sc.Forward();
186 
187 	ColouriseContext(sc, '"', SCE_ADA_STRINGEOL);
188 }
189 
ColouriseWhiteSpace(StyleContext & sc,bool &)190 static void ColouriseWhiteSpace(StyleContext& sc, bool& /*apostropheStartsAttribute*/) {
191 	// Apostrophe meaning is not changed, but the parameter is present for uniformity
192 	sc.SetState(SCE_ADA_DEFAULT);
193 	sc.ForwardSetState(SCE_ADA_DEFAULT);
194 }
195 
ColouriseWord(StyleContext & sc,WordList & keywords,bool & apostropheStartsAttribute)196 static void ColouriseWord(StyleContext& sc, WordList& keywords, bool& apostropheStartsAttribute) {
197 	apostropheStartsAttribute = true;
198 	sc.SetState(SCE_ADA_IDENTIFIER);
199 
200 	std::string word;
201 
202 	while (!sc.atLineEnd && !IsSeparatorOrDelimiterCharacter(sc.ch)) {
203 		word += static_cast<char>(tolower(sc.ch));
204 		sc.Forward();
205 	}
206 
207 	if (!IsValidIdentifier(word)) {
208 		sc.ChangeState(SCE_ADA_ILLEGAL);
209 
210 	} else if (keywords.InList(word.c_str())) {
211 		sc.ChangeState(SCE_ADA_WORD);
212 
213 		if (word != "all") {
214 			apostropheStartsAttribute = false;
215 		}
216 	}
217 
218 	sc.SetState(SCE_ADA_DEFAULT);
219 }
220 
221 //
222 // ColouriseDocument
223 //
224 
ColouriseDocument(unsigned int startPos,int length,int initStyle,WordList * keywordlists[],Accessor & styler)225 static void ColouriseDocument(
226     unsigned int startPos,
227     int length,
228     int initStyle,
229     WordList *keywordlists[],
230     Accessor &styler) {
231 	WordList &keywords = *keywordlists[0];
232 
233 	StyleContext sc(startPos, length, initStyle, styler);
234 
235 	int lineCurrent = styler.GetLine(startPos);
236 	bool apostropheStartsAttribute = (styler.GetLineState(lineCurrent) & 1) != 0;
237 
238 	while (sc.More()) {
239 		if (sc.atLineEnd) {
240 			// Go to the next line
241 			sc.Forward();
242 			lineCurrent++;
243 
244 			// Remember the line state for future incremental lexing
245 			styler.SetLineState(lineCurrent, apostropheStartsAttribute);
246 
247 			// Don't continue any styles on the next line
248 			sc.SetState(SCE_ADA_DEFAULT);
249 		}
250 
251 		// Comments
252 		if (sc.Match('-', '-')) {
253 			ColouriseComment(sc, apostropheStartsAttribute);
254 
255 		// Strings
256 		} else if (sc.Match('"')) {
257 			ColouriseString(sc, apostropheStartsAttribute);
258 
259 		// Characters
260 		} else if (sc.Match('\'') && !apostropheStartsAttribute) {
261 			ColouriseCharacter(sc, apostropheStartsAttribute);
262 
263 		// Labels
264 		} else if (sc.Match('<', '<')) {
265 			ColouriseLabel(sc, keywords, apostropheStartsAttribute);
266 
267 		// Whitespace
268 		} else if (IsASpace(sc.ch)) {
269 			ColouriseWhiteSpace(sc, apostropheStartsAttribute);
270 
271 		// Delimiters
272 		} else if (IsDelimiterCharacter(sc.ch)) {
273 			ColouriseDelimiter(sc, apostropheStartsAttribute);
274 
275 		// Numbers
276 		} else if (IsADigit(sc.ch) || sc.ch == '#') {
277 			ColouriseNumber(sc, apostropheStartsAttribute);
278 
279 		// Keywords or identifiers
280 		} else {
281 			ColouriseWord(sc, keywords, apostropheStartsAttribute);
282 		}
283 	}
284 
285 	sc.Complete();
286 }
287 
IsDelimiterCharacter(int ch)288 static inline bool IsDelimiterCharacter(int ch) {
289 	switch (ch) {
290 	case '&':
291 	case '\'':
292 	case '(':
293 	case ')':
294 	case '*':
295 	case '+':
296 	case ',':
297 	case '-':
298 	case '.':
299 	case '/':
300 	case ':':
301 	case ';':
302 	case '<':
303 	case '=':
304 	case '>':
305 	case '|':
306 		return true;
307 	default:
308 		return false;
309 	}
310 }
311 
IsNumberCharacter(int ch)312 static inline bool IsNumberCharacter(int ch) {
313 	return IsNumberStartCharacter(ch) ||
314 	       ch == '_' ||
315 	       ch == '.' ||
316 	       ch == '#' ||
317 	       (ch >= 'a' && ch <= 'f') ||
318 	       (ch >= 'A' && ch <= 'F');
319 }
320 
IsNumberStartCharacter(int ch)321 static inline bool IsNumberStartCharacter(int ch) {
322 	return IsADigit(ch);
323 }
324 
IsSeparatorOrDelimiterCharacter(int ch)325 static inline bool IsSeparatorOrDelimiterCharacter(int ch) {
326 	return IsASpace(ch) || IsDelimiterCharacter(ch);
327 }
328 
IsValidIdentifier(const std::string & identifier)329 static bool IsValidIdentifier(const std::string& identifier) {
330 	// First character can't be '_', so initialize the flag to true
331 	bool lastWasUnderscore = true;
332 
333 	size_t length = identifier.length();
334 
335 	// Zero-length identifiers are not valid (these can occur inside labels)
336 	if (length == 0) {
337 		return false;
338 	}
339 
340 	// Check for valid character at the start
341 	if (!IsWordStartCharacter(identifier[0])) {
342 		return false;
343 	}
344 
345 	// Check for only valid characters and no double underscores
346 	for (size_t i = 0; i < length; i++) {
347 		if (!IsWordCharacter(identifier[i]) ||
348 		        (identifier[i] == '_' && lastWasUnderscore)) {
349 			return false;
350 		}
351 		lastWasUnderscore = identifier[i] == '_';
352 	}
353 
354 	// Check for underscore at the end
355 	if (lastWasUnderscore == true) {
356 		return false;
357 	}
358 
359 	// All checks passed
360 	return true;
361 }
362 
IsValidNumber(const std::string & number)363 static bool IsValidNumber(const std::string& number) {
364 	size_t hashPos = number.find("#");
365 	bool seenDot = false;
366 
367 	size_t i = 0;
368 	size_t length = number.length();
369 
370 	if (length == 0)
371 		return false; // Just in case
372 
373 	// Decimal number
374 	if (hashPos == std::string::npos) {
375 		bool canBeSpecial = false;
376 
377 		for (; i < length; i++) {
378 			if (number[i] == '_') {
379 				if (!canBeSpecial) {
380 					return false;
381 				}
382 				canBeSpecial = false;
383 			} else if (number[i] == '.') {
384 				if (!canBeSpecial || seenDot) {
385 					return false;
386 				}
387 				canBeSpecial = false;
388 				seenDot = true;
389 			} else if (IsADigit(number[i])) {
390 				canBeSpecial = true;
391 			} else {
392 				break;
393 			}
394 		}
395 
396 		if (!canBeSpecial)
397 			return false;
398 	} else {
399 		// Based number
400 		bool canBeSpecial = false;
401 		int base = 0;
402 
403 		// Parse base
404 		for (; i < length; i++) {
405 			int ch = number[i];
406 			if (ch == '_') {
407 				if (!canBeSpecial)
408 					return false;
409 				canBeSpecial = false;
410 			} else if (IsADigit(ch)) {
411 				base = base * 10 + (ch - '0');
412 				if (base > 16)
413 					return false;
414 				canBeSpecial = true;
415 			} else if (ch == '#' && canBeSpecial) {
416 				break;
417 			} else {
418 				return false;
419 			}
420 		}
421 
422 		if (base < 2)
423 			return false;
424 		if (i == length)
425 			return false;
426 
427 		i++; // Skip over '#'
428 
429 		// Parse number
430 		canBeSpecial = false;
431 
432 		for (; i < length; i++) {
433 			int ch = tolower(number[i]);
434 
435 			if (ch == '_') {
436 				if (!canBeSpecial) {
437 					return false;
438 				}
439 				canBeSpecial = false;
440 
441 			} else if (ch == '.') {
442 				if (!canBeSpecial || seenDot) {
443 					return false;
444 				}
445 				canBeSpecial = false;
446 				seenDot = true;
447 
448 			} else if (IsADigit(ch)) {
449 				if (ch - '0' >= base) {
450 					return false;
451 				}
452 				canBeSpecial = true;
453 
454 			} else if (ch >= 'a' && ch <= 'f') {
455 				if (ch - 'a' + 10 >= base) {
456 					return false;
457 				}
458 				canBeSpecial = true;
459 
460 			} else if (ch == '#' && canBeSpecial) {
461 				break;
462 
463 			} else {
464 				return false;
465 			}
466 		}
467 
468 		if (i == length) {
469 			return false;
470 		}
471 
472 		i++;
473 	}
474 
475 	// Exponent (optional)
476 	if (i < length) {
477 		if (number[i] != 'e' && number[i] != 'E')
478 			return false;
479 
480 		i++; // Move past 'E'
481 
482 		if (i == length) {
483 			return false;
484 		}
485 
486 		if (number[i] == '+')
487 			i++;
488 		else if (number[i] == '-') {
489 			if (seenDot) {
490 				i++;
491 			} else {
492 				return false; // Integer literals should not have negative exponents
493 			}
494 		}
495 
496 		if (i == length) {
497 			return false;
498 		}
499 
500 		bool canBeSpecial = false;
501 
502 		for (; i < length; i++) {
503 			if (number[i] == '_') {
504 				if (!canBeSpecial) {
505 					return false;
506 				}
507 				canBeSpecial = false;
508 			} else if (IsADigit(number[i])) {
509 				canBeSpecial = true;
510 			} else {
511 				return false;
512 			}
513 		}
514 
515 		if (!canBeSpecial)
516 			return false;
517 	}
518 
519 	// if i == length, number was parsed successfully.
520 	return i == length;
521 }
522 
IsWordCharacter(int ch)523 static inline bool IsWordCharacter(int ch) {
524 	return IsWordStartCharacter(ch) || IsADigit(ch);
525 }
526 
IsWordStartCharacter(int ch)527 static inline bool IsWordStartCharacter(int ch) {
528 	return (isascii(ch) && isalpha(ch)) || ch == '_';
529 }
530