1// Scintilla source code edit control
2/** @file LexFlagship.cxx
3 ** Lexer for Harbour and FlagShip.
4 ** (Syntactically compatible to other xBase dialects, like Clipper, dBase, Clip, FoxPro etc.)
5 **/
6// Copyright 2005 by Randy Butler
7// Copyright 2010 by Xavi <jarabal/at/gmail.com> (Harbour)
8// Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
9// The License.txt file describes the conditions under which this software may be distributed.
10
11#include <stdlib.h>
12#include <string.h>
13#include <stdio.h>
14#include <stdarg.h>
15#include <assert.h>
16#include <ctype.h>
17
18#include <string>
19#include <string_view>
20
21#include "ILexer.h"
22#include "Scintilla.h"
23#include "SciLexer.h"
24
25#include "WordList.h"
26#include "LexAccessor.h"
27#include "Accessor.h"
28#include "StyleContext.h"
29#include "CharacterSet.h"
30#include "LexerModule.h"
31
32using namespace Lexilla;
33
34// Extended to accept accented characters
35static inline bool IsAWordChar(int ch)
36{
37 return ch >= 0x80 ||
38 (isalnum(ch) || ch == '_');
39}
40
41static void ColouriseFlagShipDoc(Sci_PositionU startPos, Sci_Position length, int initStyle,
42 WordList *keywordlists[], Accessor &styler)
43{
44
45 WordList &keywords = *keywordlists[0];
46 WordList &keywords2 = *keywordlists[1];
47 WordList &keywords3 = *keywordlists[2];
48 WordList &keywords4 = *keywordlists[3];
49 WordList &keywords5 = *keywordlists[4];
50
51 // property lexer.flagship.styling.within.preprocessor
52 // For Harbour code, determines whether all preprocessor code is styled in the preprocessor style (0) or only from the
53 // initial # to the end of the command word(1, the default). It also determines how to present text, dump, and disabled code.
54 bool stylingWithinPreprocessor = styler.GetPropertyInt("lexer.flagship.styling.within.preprocessor", 1) != 0;
55
56 CharacterSet setDoxygen(CharacterSet::setAlpha, "$@\\&<>#{}[]");
57
58 int visibleChars = 0;
59 int closeStringChar = 0;
60 int styleBeforeDCKeyword = SCE_FS_DEFAULT;
61 bool bEnableCode = initStyle < SCE_FS_DISABLEDCODE;
62
63 StyleContext sc(startPos, length, initStyle, styler);
64
65 for (; sc.More(); sc.Forward()) {
66
67 // Determine if the current state should terminate.
68 switch (sc.state) {
69 case SCE_FS_OPERATOR:
70 case SCE_FS_OPERATOR_C:
71 case SCE_FS_WORDOPERATOR:
72 sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
73 break;
74 case SCE_FS_IDENTIFIER:
75 case SCE_FS_IDENTIFIER_C:
76 if (!IsAWordChar(sc.ch)) {
77 char s[64];
78 sc.GetCurrentLowered(s, sizeof(s));
79 if (keywords.InList(s)) {
80 sc.ChangeState(bEnableCode ? SCE_FS_KEYWORD : SCE_FS_KEYWORD_C);
81 } else if (keywords2.InList(s)) {
82 sc.ChangeState(bEnableCode ? SCE_FS_KEYWORD2 : SCE_FS_KEYWORD2_C);
83 } else if (bEnableCode && keywords3.InList(s)) {
84 sc.ChangeState(SCE_FS_KEYWORD3);
85 } else if (bEnableCode && keywords4.InList(s)) {
86 sc.ChangeState(SCE_FS_KEYWORD4);
87 }// Else, it is really an identifier...
88 sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
89 }
90 break;
91 case SCE_FS_NUMBER:
92 if (!IsAWordChar(sc.ch) && !(sc.ch == '.' && IsADigit(sc.chNext))) {
93 sc.SetState(SCE_FS_DEFAULT);
94 }
95 break;
96 case SCE_FS_NUMBER_C:
97 if (!IsAWordChar(sc.ch) && sc.ch != '.') {
98 sc.SetState(SCE_FS_DEFAULT_C);
99 }
100 break;
101 case SCE_FS_CONSTANT:
102 if (!IsAWordChar(sc.ch)) {
103 sc.SetState(SCE_FS_DEFAULT);
104 }
105 break;
106 case SCE_FS_STRING:
107 case SCE_FS_STRING_C:
108 if (sc.ch == closeStringChar) {
109 sc.ForwardSetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
110 } else if (sc.atLineEnd) {
111 sc.ChangeState(bEnableCode ? SCE_FS_STRINGEOL : SCE_FS_STRINGEOL_C);
112 }
113 break;
114 case SCE_FS_STRINGEOL:
115 case SCE_FS_STRINGEOL_C:
116 if (sc.atLineStart) {
117 sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
118 }
119 break;
120 case SCE_FS_COMMENTDOC:
121 case SCE_FS_COMMENTDOC_C:
122 if (sc.Match('*', '/')) {
123 sc.Forward();
124 sc.ForwardSetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
125 } else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
126 // Verify that we have the conditions to mark a comment-doc-keyword
127 if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
128 styleBeforeDCKeyword = bEnableCode ? SCE_FS_COMMENTDOC : SCE_FS_COMMENTDOC_C;
129 sc.SetState(SCE_FS_COMMENTDOCKEYWORD);
130 }
131 }
132 break;
133 case SCE_FS_COMMENT:
134 case SCE_FS_COMMENTLINE:
135 if (sc.atLineStart) {
136 sc.SetState(SCE_FS_DEFAULT);
137 }
138 break;
139 case SCE_FS_COMMENTLINEDOC:
140 case SCE_FS_COMMENTLINEDOC_C:
141 if (sc.atLineStart) {
142 sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
143 } else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
144 // Verify that we have the conditions to mark a comment-doc-keyword
145 if ((IsASpace(sc.chPrev) || sc.chPrev == '/' || sc.chPrev == '!') && (!IsASpace(sc.chNext))) {
146 styleBeforeDCKeyword = bEnableCode ? SCE_FS_COMMENTLINEDOC : SCE_FS_COMMENTLINEDOC_C;
147 sc.SetState(SCE_FS_COMMENTDOCKEYWORD);
148 }
149 }
150 break;
151 case SCE_FS_COMMENTDOCKEYWORD:
152 if ((styleBeforeDCKeyword == SCE_FS_COMMENTDOC || styleBeforeDCKeyword == SCE_FS_COMMENTDOC_C) &&
153 sc.Match('*', '/')) {
154 sc.ChangeState(SCE_FS_COMMENTDOCKEYWORDERROR);
155 sc.Forward();
156 sc.ForwardSetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
157 } else if (!setDoxygen.Contains(sc.ch)) {
158 char s[64];
159 sc.GetCurrentLowered(s, sizeof(s));
160 if (!IsASpace(sc.ch) || !keywords5.InList(s + 1)) {
161 sc.ChangeState(SCE_FS_COMMENTDOCKEYWORDERROR);
162 }
163 sc.SetState(styleBeforeDCKeyword);
164 }
165 break;
166 case SCE_FS_PREPROCESSOR:
167 case SCE_FS_PREPROCESSOR_C:
168 if (sc.atLineEnd) {
169 if (!(sc.chPrev == ';' || sc.GetRelative(-2) == ';')) {
170 sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
171 }
172 } else if (stylingWithinPreprocessor) {
173 if (IsASpaceOrTab(sc.ch)) {
174 sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
175 }
176 } else if (sc.Match('/', '*') || sc.Match('/', '/') || sc.Match('&', '&')) {
177 sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
178 }
179 break;
180 case SCE_FS_DISABLEDCODE:
181 if (sc.ch == '#' && visibleChars == 0) {
182 sc.SetState(bEnableCode ? SCE_FS_PREPROCESSOR : SCE_FS_PREPROCESSOR_C);
183 do { // Skip whitespace between # and preprocessor word
184 sc.Forward();
185 } while (IsASpaceOrTab(sc.ch) && sc.More());
186 if (sc.MatchIgnoreCase("pragma")) {
187 sc.Forward(6);
188 do { // Skip more whitespace until keyword
189 sc.Forward();
190 } while (IsASpaceOrTab(sc.ch) && sc.More());
191 if (sc.MatchIgnoreCase("enddump") || sc.MatchIgnoreCase("__endtext")) {
192 bEnableCode = true;
193 sc.SetState(SCE_FS_DISABLEDCODE);
194 sc.Forward(sc.ch == '_' ? 8 : 6);
195 sc.ForwardSetState(SCE_FS_DEFAULT);
196 } else {
197 sc.ChangeState(SCE_FS_DISABLEDCODE);
198 }
199 } else {
200 sc.ChangeState(SCE_FS_DISABLEDCODE);
201 }
202 }
203 break;
204 case SCE_FS_DATE:
205 if (sc.ch == '}') {
206 sc.ForwardSetState(SCE_FS_DEFAULT);
207 } else if (sc.atLineEnd) {
208 sc.ChangeState(SCE_FS_STRINGEOL);
209 }
210 }
211
212 // Determine if a new state should be entered.
213 if (sc.state == SCE_FS_DEFAULT || sc.state == SCE_FS_DEFAULT_C) {
214 if (bEnableCode &&
215 (sc.MatchIgnoreCase(".and.") || sc.MatchIgnoreCase(".not."))) {
216 sc.SetState(SCE_FS_WORDOPERATOR);
217 sc.Forward(4);
218 } else if (bEnableCode && sc.MatchIgnoreCase(".or.")) {
219 sc.SetState(SCE_FS_WORDOPERATOR);
220 sc.Forward(3);
221 } else if (bEnableCode &&
222 (sc.MatchIgnoreCase(".t.") || sc.MatchIgnoreCase(".f.") ||
223 (!IsAWordChar(sc.GetRelative(3)) && sc.MatchIgnoreCase("nil")))) {
224 sc.SetState(SCE_FS_CONSTANT);
225 sc.Forward(2);
226 } else if (sc.Match('/', '*')) {
227 sc.SetState(bEnableCode ? SCE_FS_COMMENTDOC : SCE_FS_COMMENTDOC_C);
228 sc.Forward();
229 } else if (bEnableCode && sc.Match('&', '&')) {
230 sc.SetState(SCE_FS_COMMENTLINE);
231 sc.Forward();
232 } else if (sc.Match('/', '/')) {
233 sc.SetState(bEnableCode ? SCE_FS_COMMENTLINEDOC : SCE_FS_COMMENTLINEDOC_C);
234 sc.Forward();
235 } else if (bEnableCode && sc.ch == '*' && visibleChars == 0) {
236 sc.SetState(SCE_FS_COMMENT);
237 } else if (sc.ch == '\"' || sc.ch == '\'') {
238 sc.SetState(bEnableCode ? SCE_FS_STRING : SCE_FS_STRING_C);
239 closeStringChar = sc.ch;
240 } else if (closeStringChar == '>' && sc.ch == '<') {
241 sc.SetState(bEnableCode ? SCE_FS_STRING : SCE_FS_STRING_C);
242 } else if (sc.ch == '#' && visibleChars == 0) {
243 sc.SetState(bEnableCode ? SCE_FS_PREPROCESSOR : SCE_FS_PREPROCESSOR_C);
244 do { // Skip whitespace between # and preprocessor word
245 sc.Forward();
246 } while (IsASpaceOrTab(sc.ch) && sc.More());
247 if (sc.atLineEnd) {
248 sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
249 } else if (sc.MatchIgnoreCase("include")) {
250 if (stylingWithinPreprocessor) {
251 closeStringChar = '>';
252 }
253 } else if (sc.MatchIgnoreCase("pragma")) {
254 sc.Forward(6);
255 do { // Skip more whitespace until keyword
256 sc.Forward();
257 } while (IsASpaceOrTab(sc.ch) && sc.More());
258 if (sc.MatchIgnoreCase("begindump") || sc.MatchIgnoreCase("__cstream")) {
259 bEnableCode = false;
260 if (stylingWithinPreprocessor) {
261 sc.SetState(SCE_FS_DISABLEDCODE);
262 sc.Forward(8);
263 sc.ForwardSetState(SCE_FS_DEFAULT_C);
264 } else {
265 sc.SetState(SCE_FS_DISABLEDCODE);
266 }
267 } else if (sc.MatchIgnoreCase("enddump") || sc.MatchIgnoreCase("__endtext")) {
268 bEnableCode = true;
269 sc.SetState(SCE_FS_DISABLEDCODE);
270 sc.Forward(sc.ch == '_' ? 8 : 6);
271 sc.ForwardSetState(SCE_FS_DEFAULT);
272 }
273 }
274 } else if (bEnableCode && sc.ch == '{') {
275 Sci_Position p = 0;
276 int chSeek;
277 Sci_PositionU endPos(startPos + length);
278 do { // Skip whitespace
279 chSeek = sc.GetRelative(++p);
280 } while (IsASpaceOrTab(chSeek) && (sc.currentPos + p < endPos));
281 if (chSeek == '^') {
282 sc.SetState(SCE_FS_DATE);
283 } else {
284 sc.SetState(SCE_FS_OPERATOR);
285 }
286 } else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
287 sc.SetState(bEnableCode ? SCE_FS_NUMBER : SCE_FS_NUMBER_C);
288 } else if (IsAWordChar(sc.ch)) {
289 sc.SetState(bEnableCode ? SCE_FS_IDENTIFIER : SCE_FS_IDENTIFIER_C);
290 } else if (isoperator(static_cast<char>(sc.ch)) || (bEnableCode && sc.ch == '@')) {
291 sc.SetState(bEnableCode ? SCE_FS_OPERATOR : SCE_FS_OPERATOR_C);
292 }
293 }
294
295 if (sc.atLineEnd) {
296 visibleChars = 0;
297 closeStringChar = 0;
298 }
299 if (!IsASpace(sc.ch)) {
300 visibleChars++;
301 }
302 }
303 sc.Complete();
304}
305
306static void FoldFlagShipDoc(Sci_PositionU startPos, Sci_Position length, int,
307 WordList *[], Accessor &styler)
308{
309
310 Sci_Position endPos = startPos + length;
311
312 // Backtrack to previous line in case need to fix its fold status
313 Sci_Position lineCurrent = styler.GetLine(startPos);
314 if (startPos > 0 && lineCurrent > 0) {
315 lineCurrent--;
316 startPos = styler.LineStart(lineCurrent);
317 }
318 int spaceFlags = 0;
319 int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags);
320 char chNext = styler[startPos];
321 for (Sci_Position i = startPos; i < endPos; i++) {
322 char ch = chNext;
323 chNext = styler.SafeGetCharAt(i + 1);
324
325 if ((ch == '\r' && chNext != '\n') || (ch == '\n') || (i == endPos-1)) {
326 int lev = indentCurrent;
327 int indentNext = styler.IndentAmount(lineCurrent + 1, &spaceFlags);
328 if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
329 if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK)) {
330 lev |= SC_FOLDLEVELHEADERFLAG;
331 } else if (indentNext & SC_FOLDLEVELWHITEFLAG) {
332 int spaceFlags2 = 0;
333 int indentNext2 = styler.IndentAmount(lineCurrent + 2, &spaceFlags2);
334 if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext2 & SC_FOLDLEVELNUMBERMASK)) {
335 lev |= SC_FOLDLEVELHEADERFLAG;
336 }
337 }
338 }
339 indentCurrent = indentNext;
340 styler.SetLevel(lineCurrent, lev);
341 lineCurrent++;
342 }
343 }
344}
345
346static const char * const FSWordListDesc[] = {
347 "Keywords Commands",
348 "Std Library Functions",
349 "Procedure, return, exit",
350 "Class (oop)",
351 "Doxygen keywords",
352 0
353};
354
355LexerModule lmFlagShip(SCLEX_FLAGSHIP, ColouriseFlagShipDoc, "flagship", FoldFlagShipDoc, FSWordListDesc);
356