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