1// Scintilla source code edit control
2/** @file LexPOV.cxx
3 ** Lexer for POV-Ray SDL (Persistance of Vision Raytracer, Scene Description Language).
4 ** Written by Philippe Lhoste but this is mostly a derivative of LexCPP...
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// Some points that distinguish from a simple C lexer:
10// Identifiers start only by a character.
11// No line continuation character.
12// Strings are limited to 256 characters.
13// Directives are similar to preprocessor commands,
14// but we match directive keywords and colorize incorrect ones.
15// Block comments can be nested (code stolen from my code in LexLua).
16
17#include <stdlib.h>
18#include <string.h>
19#include <stdio.h>
20#include <stdarg.h>
21#include <assert.h>
22#include <ctype.h>
23
24#include <string>
25#include <string_view>
26
27#include "ILexer.h"
28#include "Scintilla.h"
29#include "SciLexer.h"
30
31#include "WordList.h"
32#include "LexAccessor.h"
33#include "Accessor.h"
34#include "StyleContext.h"
35#include "CharacterSet.h"
36#include "LexerModule.h"
37
38using namespace Lexilla;
39
40static inline bool IsAWordChar(int ch) {
41 return ch < 0x80 && (isalnum(ch) || ch == '_');
42}
43
44static inline bool IsAWordStart(int ch) {
45 return ch < 0x80 && isalpha(ch);
46}
47
48static inline bool IsANumberChar(int ch) {
49 // Not exactly following number definition (several dots are seen as OK, etc.)
50 // but probably enough in most cases.
51 return (ch < 0x80) &&
52 (isdigit(ch) || toupper(ch) == 'E' ||
53 ch == '.' || ch == '-' || ch == '+');
54}
55
56static void ColourisePovDoc(
57 Sci_PositionU startPos,
58 Sci_Position length,
59 int initStyle,
60 WordList *keywordlists[],
61 Accessor &styler) {
62
63 WordList &keywords1 = *keywordlists[0];
64 WordList &keywords2 = *keywordlists[1];
65 WordList &keywords3 = *keywordlists[2];
66 WordList &keywords4 = *keywordlists[3];
67 WordList &keywords5 = *keywordlists[4];
68 WordList &keywords6 = *keywordlists[5];
69 WordList &keywords7 = *keywordlists[6];
70 WordList &keywords8 = *keywordlists[7];
71
72 Sci_Position currentLine = styler.GetLine(startPos);
73 // Initialize the block comment /* */ nesting level, if we are inside such a comment.
74 int blockCommentLevel = 0;
75 if (initStyle == SCE_POV_COMMENT) {
76 blockCommentLevel = styler.GetLineState(currentLine - 1);
77 }
78
79 // Do not leak onto next line
80 if (initStyle == SCE_POV_STRINGEOL || initStyle == SCE_POV_COMMENTLINE) {
81 initStyle = SCE_POV_DEFAULT;
82 }
83
84 short stringLen = 0;
85
86 StyleContext sc(startPos, length, initStyle, styler);
87
88 for (; sc.More(); sc.Forward()) {
89 if (sc.atLineEnd) {
90 // Update the line state, so it can be seen by next line
91 currentLine = styler.GetLine(sc.currentPos);
92 if (sc.state == SCE_POV_COMMENT) {
93 // Inside a block comment, we set the line state
94 styler.SetLineState(currentLine, blockCommentLevel);
95 } else {
96 // Reset the line state
97 styler.SetLineState(currentLine, 0);
98 }
99 }
100
101 if (sc.atLineStart && (sc.state == SCE_POV_STRING)) {
102 // Prevent SCE_POV_STRINGEOL from leaking back to previous line
103 sc.SetState(SCE_POV_STRING);
104 }
105
106 // Determine if the current state should terminate.
107 if (sc.state == SCE_POV_OPERATOR) {
108 sc.SetState(SCE_POV_DEFAULT);
109 } else if (sc.state == SCE_POV_NUMBER) {
110 // We stop the number definition on non-numerical non-dot non-eE non-sign char
111 if (!IsANumberChar(sc.ch)) {
112 sc.SetState(SCE_POV_DEFAULT);
113 }
114 } else if (sc.state == SCE_POV_IDENTIFIER) {
115 if (!IsAWordChar(sc.ch)) {
116 char s[100];
117 sc.GetCurrent(s, sizeof(s));
118 if (keywords2.InList(s)) {
119 sc.ChangeState(SCE_POV_WORD2);
120 } else if (keywords3.InList(s)) {
121 sc.ChangeState(SCE_POV_WORD3);
122 } else if (keywords4.InList(s)) {
123 sc.ChangeState(SCE_POV_WORD4);
124 } else if (keywords5.InList(s)) {
125 sc.ChangeState(SCE_POV_WORD5);
126 } else if (keywords6.InList(s)) {
127 sc.ChangeState(SCE_POV_WORD6);
128 } else if (keywords7.InList(s)) {
129 sc.ChangeState(SCE_POV_WORD7);
130 } else if (keywords8.InList(s)) {
131 sc.ChangeState(SCE_POV_WORD8);
132 }
133 sc.SetState(SCE_POV_DEFAULT);
134 }
135 } else if (sc.state == SCE_POV_DIRECTIVE) {
136 if (!IsAWordChar(sc.ch)) {
137 char s[100];
138 char *p;
139 sc.GetCurrent(s, sizeof(s));
140 p = s;
141 // Skip # and whitespace between # and directive word
142 do {
143 p++;
144 } while ((*p == ' ' || *p == '\t') && *p != '\0');
145 if (!keywords1.InList(p)) {
146 sc.ChangeState(SCE_POV_BADDIRECTIVE);
147 }
148 sc.SetState(SCE_POV_DEFAULT);
149 }
150 } else if (sc.state == SCE_POV_COMMENT) {
151 if (sc.Match('/', '*')) {
152 blockCommentLevel++;
153 sc.Forward();
154 } else if (sc.Match('*', '/') && blockCommentLevel > 0) {
155 blockCommentLevel--;
156 sc.Forward();
157 if (blockCommentLevel == 0) {
158 sc.ForwardSetState(SCE_POV_DEFAULT);
159 }
160 }
161 } else if (sc.state == SCE_POV_COMMENTLINE) {
162 if (sc.atLineEnd) {
163 sc.ForwardSetState(SCE_POV_DEFAULT);
164 }
165 } else if (sc.state == SCE_POV_STRING) {
166 if (sc.ch == '\\') {
167 stringLen++;
168 if (strchr("abfnrtuv0'\"", sc.chNext)) {
169 // Compound characters are counted as one.
170 // Note: for Unicode chars \u, we shouldn't count the next 4 digits...
171 sc.Forward();
172 }
173 } else if (sc.ch == '\"') {
174 sc.ForwardSetState(SCE_POV_DEFAULT);
175 } else if (sc.atLineEnd) {
176 sc.ChangeState(SCE_POV_STRINGEOL);
177 sc.ForwardSetState(SCE_POV_DEFAULT);
178 } else {
179 stringLen++;
180 }
181 if (stringLen > 256) {
182 // Strings are limited to 256 chars
183 sc.SetState(SCE_POV_STRINGEOL);
184 }
185 } else if (sc.state == SCE_POV_STRINGEOL) {
186 if (sc.ch == '\\') {
187 if (sc.chNext == '\"' || sc.chNext == '\\') {
188 sc.Forward();
189 }
190 } else if (sc.ch == '\"') {
191 sc.ForwardSetState(SCE_C_DEFAULT);
192 } else if (sc.atLineEnd) {
193 sc.ForwardSetState(SCE_POV_DEFAULT);
194 }
195 }
196
197 // Determine if a new state should be entered.
198 if (sc.state == SCE_POV_DEFAULT) {
199 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
200 sc.SetState(SCE_POV_NUMBER);
201 } else if (IsAWordStart(sc.ch)) {
202 sc.SetState(SCE_POV_IDENTIFIER);
203 } else if (sc.Match('/', '*')) {
204 blockCommentLevel = 1;
205 sc.SetState(SCE_POV_COMMENT);
206 sc.Forward(); // Eat the * so it isn't used for the end of the comment
207 } else if (sc.Match('/', '/')) {
208 sc.SetState(SCE_POV_COMMENTLINE);
209 } else if (sc.ch == '\"') {
210 sc.SetState(SCE_POV_STRING);
211 stringLen = 0;
212 } else if (sc.ch == '#') {
213 sc.SetState(SCE_POV_DIRECTIVE);
214 // Skip whitespace between # and directive word
215 do {
216 sc.Forward();
217 } while ((sc.ch == ' ' || sc.ch == '\t') && sc.More());
218 if (sc.atLineEnd) {
219 sc.SetState(SCE_POV_DEFAULT);
220 }
221 } else if (isoperator(static_cast<char>(sc.ch))) {
222 sc.SetState(SCE_POV_OPERATOR);
223 }
224 }
225 }
226 sc.Complete();
227}
228
229static void FoldPovDoc(
230 Sci_PositionU startPos,
231 Sci_Position length,
232 int initStyle,
233 WordList *[],
234 Accessor &styler) {
235
236 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
237 bool foldDirective = styler.GetPropertyInt("fold.directive") != 0;
238 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
239 Sci_PositionU endPos = startPos + length;
240 int visibleChars = 0;
241 Sci_Position lineCurrent = styler.GetLine(startPos);
242 int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
243 int levelCurrent = levelPrev;
244 char chNext = styler[startPos];
245 int styleNext = styler.StyleAt(startPos);
246 int style = initStyle;
247 for (Sci_PositionU i = startPos; i < endPos; i++) {
248 char ch = chNext;
249 chNext = styler.SafeGetCharAt(i + 1);
250 int stylePrev = style;
251 style = styleNext;
252 styleNext = styler.StyleAt(i + 1);
253 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
254 if (foldComment && (style == SCE_POV_COMMENT)) {
255 if (stylePrev != SCE_POV_COMMENT) {
256 levelCurrent++;
257 } else if ((styleNext != SCE_POV_COMMENT) && !atEOL) {
258 // Comments don't end at end of line and the next character may be unstyled.
259 levelCurrent--;
260 }
261 }
262 if (foldComment && (style == SCE_POV_COMMENTLINE)) {
263 if ((ch == '/') && (chNext == '/')) {
264 char chNext2 = styler.SafeGetCharAt(i + 2);
265 if (chNext2 == '{') {
266 levelCurrent++;
267 } else if (chNext2 == '}') {
268 levelCurrent--;
269 }
270 }
271 }
272 if (foldDirective && (style == SCE_POV_DIRECTIVE)) {
273 if (ch == '#') {
274 Sci_PositionU j=i+1;
275 while ((j<endPos) && IsASpaceOrTab(styler.SafeGetCharAt(j))) {
276 j++;
277 }
278 }
279 }
280 if (style == SCE_POV_OPERATOR) {
281 if (ch == '{') {
282 levelCurrent++;
283 } else if (ch == '}') {
284 levelCurrent--;
285 }
286 }
287 if (atEOL) {
288 int lev = levelPrev;
289 if (visibleChars == 0 && foldCompact)
290 lev |= SC_FOLDLEVELWHITEFLAG;
291 if ((levelCurrent > levelPrev) && (visibleChars > 0))
292 lev |= SC_FOLDLEVELHEADERFLAG;
293 if (lev != styler.LevelAt(lineCurrent)) {
294 styler.SetLevel(lineCurrent, lev);
295 }
296 lineCurrent++;
297 levelPrev = levelCurrent;
298 visibleChars = 0;
299 }
300 if (!isspacechar(ch))
301 visibleChars++;
302 }
303 // Fill in the real level of the next line, keeping the current flags as they will be filled in later
304 int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
305 styler.SetLevel(lineCurrent, levelPrev | flagsNext);
306}
307
308static const char * const povWordLists[] = {
309 "Language directives",
310 "Objects & CSG & Appearance",
311 "Types & Modifiers & Items",
312 "Predefined Identifiers",
313 "Predefined Functions",
314 "User defined 1",
315 "User defined 2",
316 "User defined 3",
317 0,
318};
319
320LexerModule lmPOV(SCLEX_POV, ColourisePovDoc, "pov", FoldPovDoc, povWordLists);
321