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