1// Scintilla source code edit control
2/**
3 * @file LexMagik.cxx
4 * Lexer for GE(r) Smallworld(tm) MagikSF
5 */
6// Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
7// The License.txt file describes the conditions under which this software may be distributed.
8
9#include <stdlib.h>
10#include <string.h>
11#include <stdio.h>
12#include <stdarg.h>
13#include <assert.h>
14#include <ctype.h>
15
16#include <string>
17#include <string_view>
18
19#include "ILexer.h"
20#include "Scintilla.h"
21#include "SciLexer.h"
22
23#include "WordList.h"
24#include "LexAccessor.h"
25#include "Accessor.h"
26#include "StyleContext.h"
27#include "CharacterSet.h"
28#include "LexerModule.h"
29
30using namespace Lexilla;
31
32/**
33 * Is it a core character (C isalpha(), exclamation and question mark)
34 *
35 * \param ch The character
36 * \return True if ch is a character, False otherwise
37 */
38static inline bool IsAlphaCore(int ch) {
39 return (isalpha(ch) || ch == '!' || ch == '?');
40}
41
42/**
43 * Is it a character (IsAlphaCore() and underscore)
44 *
45 * \param ch The character
46 * \return True if ch is a character, False otherwise
47 */
48static inline bool IsAlpha(int ch) {
49 return (IsAlphaCore(ch) || ch == '_');
50}
51
52/**
53 * Is it a symbolic character (IsAlpha() and colon)
54 *
55 * \param ch The character
56 * \return True if ch is a character, False otherwise
57 */
58static inline bool IsAlphaSym(int ch) {
59 return (IsAlpha(ch) || ch == ':');
60}
61
62/**
63 * Is it a numerical character (IsAlpha() and 0 - 9)
64 *
65 * \param ch The character
66 * \return True if ch is a character, False otherwise
67 */
68static inline bool IsAlNum(int ch) {
69 return ((ch >= '0' && ch <= '9') || IsAlpha(ch));
70}
71
72/**
73 * Is it a symbolic numerical character (IsAlNum() and colon)
74 *
75 * \param ch The character
76 * \return True if ch is a character, False otherwise
77 */
78static inline bool IsAlNumSym(int ch) {
79 return (IsAlNum(ch) || ch == ':');
80}
81
82/**
83 * The lexer function
84 *
85 * \param startPos Where to start scanning
86 * \param length Where to scan to
87 * \param initStyle The style at the initial point, not used in this folder
88 * \param keywordlists The keywordslists, currently, number 5 is used
89 * \param styler The styler
90 */
91static void ColouriseMagikDoc(Sci_PositionU startPos, Sci_Position length, int initStyle,
92 WordList *keywordlists[], Accessor &styler) {
93 styler.StartAt(startPos);
94
95 WordList &keywords = *keywordlists[0];
96 WordList &pragmatics = *keywordlists[1];
97 WordList &containers = *keywordlists[2];
98 WordList &flow = *keywordlists[3];
99 WordList &characters = *keywordlists[4];
100
101 StyleContext sc(startPos, length, initStyle, styler);
102
103
104 for (; sc.More(); sc.Forward()) {
105
106 repeat:
107
108 if(sc.ch == '#') {
109 if (sc.chNext == '#') sc.SetState(SCE_MAGIK_HYPER_COMMENT);
110 else sc.SetState(SCE_MAGIK_COMMENT);
111 for(; sc.More() && !(sc.atLineEnd); sc.Forward());
112 sc.SetState(SCE_MAGIK_DEFAULT);
113 goto repeat;
114 }
115
116 if(sc.ch == '"') {
117 sc.SetState(SCE_MAGIK_STRING);
118
119 if(sc.More())
120 {
121 sc.Forward();
122 for(; sc.More() && sc.ch != '"'; sc.Forward());
123 }
124
125 sc.ForwardSetState(SCE_MAGIK_DEFAULT);
126 goto repeat;
127 }
128
129 // The default state
130 if(sc.state == SCE_MAGIK_DEFAULT) {
131
132 // A certain keyword has been detected
133 if (sc.ch == '_' && (
134 sc.currentPos == 0 || !IsAlNum(sc.chPrev))) {
135 char keyword[50];
136 memset(keyword, '\0', 50);
137
138 for(
139 int scanPosition = 0;
140 scanPosition < 50;
141 scanPosition++) {
142 char keywordChar = static_cast<char>(
143 tolower(styler.SafeGetCharAt(
144 scanPosition +
145 static_cast<Sci_Position>(sc.currentPos+1), ' ')));
146 if(IsAlpha(keywordChar)) {
147 keyword[scanPosition] = keywordChar;
148 } else {
149 break;
150 }
151 }
152
153 // It is a pragma
154 if(pragmatics.InList(keyword)) {
155 sc.SetState(SCE_MAGIK_PRAGMA);
156 }
157
158 // it is a normal keyword like _local, _self, etc.
159 else if(keywords.InList(keyword)) {
160 sc.SetState(SCE_MAGIK_KEYWORD);
161 }
162
163 // It is a container keyword, such as _method, _proc, etc.
164 else if(containers.InList(keyword)) {
165 sc.SetState(SCE_MAGIK_CONTAINER);
166 }
167
168 // It is a flow keyword, such as _for, _if, _try, etc.
169 else if(flow.InList(keyword)) {
170 sc.SetState(SCE_MAGIK_FLOW);
171 }
172
173 // Interpret as unknown keyword
174 else {
175 sc.SetState(SCE_MAGIK_UNKNOWN_KEYWORD);
176 }
177 }
178
179 // Symbolic expression
180 else if(sc.ch == ':' && !IsAlNum(sc.chPrev)) {
181 sc.SetState(SCE_MAGIK_SYMBOL);
182 bool firstTrip = true;
183 for(sc.Forward(); sc.More(); sc.Forward()) {
184 if(firstTrip && IsAlphaSym(sc.ch));
185 else if(!firstTrip && IsAlNumSym(sc.ch));
186 else if(sc.ch == '|') {
187 for(sc.Forward();
188 sc.More() && sc.ch != '|';
189 sc.Forward());
190 }
191 else break;
192
193 firstTrip = false;
194 }
195 sc.SetState(SCE_MAGIK_DEFAULT);
196 goto repeat;
197 }
198
199 // Identifier (label) expression
200 else if(sc.ch == '@') {
201 sc.SetState(SCE_MAGIK_IDENTIFIER);
202 bool firstTrip = true;
203 for(sc.Forward(); sc.More(); sc.Forward()) {
204 if(firstTrip && IsAlphaCore(sc.ch)) {
205 firstTrip = false;
206 }
207 else if(!firstTrip && IsAlpha(sc.ch));
208 else break;
209 }
210 sc.SetState(SCE_MAGIK_DEFAULT);
211 goto repeat;
212 }
213
214 // Start of a character
215 else if(sc.ch == '%') {
216 sc.SetState(SCE_MAGIK_CHARACTER);
217 sc.Forward();
218 char keyword[50];
219 memset(keyword, '\0', 50);
220
221 for(
222 int scanPosition = 0;
223 scanPosition < 50;
224 scanPosition++) {
225 char keywordChar = static_cast<char>(
226 tolower(styler.SafeGetCharAt(
227 scanPosition +
228 static_cast<int>(sc.currentPos), ' ')));
229 if(IsAlpha(keywordChar)) {
230 keyword[scanPosition] = keywordChar;
231 } else {
232 break;
233 }
234 }
235
236 if(characters.InList(keyword)) {
237 sc.Forward(static_cast<int>(strlen(keyword)));
238 } else {
239 sc.Forward();
240 }
241
242 sc.SetState(SCE_MAGIK_DEFAULT);
243 goto repeat;
244 }
245
246 // Operators
247 else if(
248 sc.ch == '>' ||
249 sc.ch == '<' ||
250 sc.ch == '.' ||
251 sc.ch == ',' ||
252 sc.ch == '+' ||
253 sc.ch == '-' ||
254 sc.ch == '/' ||
255 sc.ch == '*' ||
256 sc.ch == '~' ||
257 sc.ch == '$' ||
258 sc.ch == '=') {
259 sc.SetState(SCE_MAGIK_OPERATOR);
260 }
261
262 // Braces
263 else if(sc.ch == '(' || sc.ch == ')') {
264 sc.SetState(SCE_MAGIK_BRACE_BLOCK);
265 }
266
267 // Brackets
268 else if(sc.ch == '{' || sc.ch == '}') {
269 sc.SetState(SCE_MAGIK_BRACKET_BLOCK);
270 }
271
272 // Square Brackets
273 else if(sc.ch == '[' || sc.ch == ']') {
274 sc.SetState(SCE_MAGIK_SQBRACKET_BLOCK);
275 }
276
277
278 }
279
280 // It is an operator
281 else if(
282 sc.state == SCE_MAGIK_OPERATOR ||
283 sc.state == SCE_MAGIK_BRACE_BLOCK ||
284 sc.state == SCE_MAGIK_BRACKET_BLOCK ||
285 sc.state == SCE_MAGIK_SQBRACKET_BLOCK) {
286 sc.SetState(SCE_MAGIK_DEFAULT);
287 goto repeat;
288 }
289
290 // It is the pragma state
291 else if(sc.state == SCE_MAGIK_PRAGMA) {
292 if(!IsAlpha(sc.ch)) {
293 sc.SetState(SCE_MAGIK_DEFAULT);
294 goto repeat;
295 }
296 }
297
298 // It is the keyword state
299 else if(
300 sc.state == SCE_MAGIK_KEYWORD ||
301 sc.state == SCE_MAGIK_CONTAINER ||
302 sc.state == SCE_MAGIK_FLOW ||
303 sc.state == SCE_MAGIK_UNKNOWN_KEYWORD) {
304 if(!IsAlpha(sc.ch)) {
305 sc.SetState(SCE_MAGIK_DEFAULT);
306 goto repeat;
307 }
308 }
309 }
310
311 sc.Complete();
312}
313
314/**
315 * The word list description
316 */
317static const char * const magikWordListDesc[] = {
318 "Accessors (local, global, self, super, thisthread)",
319 "Pragmatic (pragma, private)",
320 "Containers (method, block, proc)",
321 "Flow (if, then, elif, else)",
322 "Characters (space, tab, newline, return)",
323 "Fold Containers (method, proc, block, if, loop)",
324 0};
325
326/**
327 * This function detects keywords which are able to have a body. Note that it
328 * uses the Fold Containers word description, not the containers description. It
329 * only works when the style at that particular position is set on Containers
330 * or Flow (number 3 or 4).
331 *
332 * \param keywordslist The list of keywords that are scanned, they should only
333 * contain the start keywords, not the end keywords
334 * \param keyword The actual keyword
335 * \return 1 if it is a folding start-keyword, -1 if it is a folding end-keyword
336 * 0 otherwise
337 */
338static inline int IsFoldingContainer(WordList &keywordslist, char * keyword) {
339 if(
340 strlen(keyword) > 3 &&
341 keyword[0] == 'e' && keyword[1] == 'n' && keyword[2] == 'd') {
342 if (keywordslist.InList(keyword + 3)) {
343 return -1;
344 }
345
346 } else {
347 if(keywordslist.InList(keyword)) {
348 return 1;
349 }
350 }
351
352 return 0;
353}
354
355/**
356 * The folding function
357 *
358 * \param startPos Where to start scanning
359 * \param length Where to scan to
360 * \param keywordslists The keywordslists, currently, number 5 is used
361 * \param styler The styler
362 */
363static void FoldMagikDoc(Sci_PositionU startPos, Sci_Position length, int,
364 WordList *keywordslists[], Accessor &styler) {
365
366 bool compact = styler.GetPropertyInt("fold.compact") != 0;
367
368 WordList &foldingElements = *keywordslists[5];
369 Sci_Position endPos = startPos + length;
370 Sci_Position line = styler.GetLine(startPos);
371 int level = styler.LevelAt(line) & SC_FOLDLEVELNUMBERMASK;
372 int flags = styler.LevelAt(line) & ~SC_FOLDLEVELNUMBERMASK;
373
374 for(
375 Sci_Position currentPos = startPos;
376 currentPos < endPos;
377 currentPos++) {
378 char currentState = styler.StyleAt(currentPos);
379 char c = styler.SafeGetCharAt(currentPos, ' ');
380 Sci_Position prevLine = styler.GetLine(currentPos - 1);
381 line = styler.GetLine(currentPos);
382
383 // Default situation
384 if(prevLine < line) {
385 styler.SetLevel(line, (level|flags) & ~SC_FOLDLEVELHEADERFLAG);
386 flags = styler.LevelAt(line) & ~SC_FOLDLEVELNUMBERMASK;
387 }
388
389 if(
390 (
391 currentState == SCE_MAGIK_CONTAINER ||
392 currentState == SCE_MAGIK_FLOW
393 ) &&
394 c == '_') {
395
396 char keyword[50];
397 memset(keyword, '\0', 50);
398
399 for(
400 int scanPosition = 0;
401 scanPosition < 50;
402 scanPosition++) {
403 char keywordChar = static_cast<char>(
404 tolower(styler.SafeGetCharAt(
405 scanPosition +
406 currentPos + 1, ' ')));
407 if(IsAlpha(keywordChar)) {
408 keyword[scanPosition] = keywordChar;
409 } else {
410 break;
411 }
412 }
413
414 if(IsFoldingContainer(foldingElements, keyword) > 0) {
415 styler.SetLevel(
416 line,
417 styler.LevelAt(line) | SC_FOLDLEVELHEADERFLAG);
418 level++;
419 } else if(IsFoldingContainer(foldingElements, keyword) < 0) {
420 styler.SetLevel(line, styler.LevelAt(line));
421 level--;
422 }
423 }
424
425 if(
426 compact && (
427 currentState == SCE_MAGIK_BRACE_BLOCK ||
428 currentState == SCE_MAGIK_BRACKET_BLOCK ||
429 currentState == SCE_MAGIK_SQBRACKET_BLOCK)) {
430 if(c == '{' || c == '[' || c == '(') {
431 styler.SetLevel(
432 line,
433 styler.LevelAt(line) | SC_FOLDLEVELHEADERFLAG);
434 level++;
435 } else if(c == '}' || c == ']' || c == ')') {
436 styler.SetLevel(line, styler.LevelAt(line));
437 level--;
438 }
439 }
440 }
441
442}
443
444/**
445 * Injecting the module
446 */
447LexerModule lmMagikSF(
448 SCLEX_MAGIK, ColouriseMagikDoc, "magiksf", FoldMagikDoc, magikWordListDesc);
449
450