1// Scintilla source code edit control
2/**
3 * @file LexRegistry.cxx
4 * @date July 26 2014
5 * @brief Lexer for Windows registration files(.reg)
6 * @author nkmathew
7 *
8 * The License.txt file describes the conditions under which this software may be
9 * distributed.
10 *
11 */
12
13#include <cstdlib>
14#include <cassert>
15#include <cctype>
16#include <cstdio>
17
18#include <string>
19#include <string_view>
20#include <vector>
21#include <map>
22#include <functional>
23
24#include "ILexer.h"
25#include "Scintilla.h"
26#include "SciLexer.h"
27#include "WordList.h"
28#include "LexAccessor.h"
29#include "StyleContext.h"
30#include "CharacterSet.h"
31#include "LexerModule.h"
32#include "OptionSet.h"
33#include "DefaultLexer.h"
34
35using namespace Scintilla;
36using namespace Lexilla;
37
38static const char *const RegistryWordListDesc[] = {
39 0
40};
41
42struct OptionsRegistry {
43 bool foldCompact;
44 bool fold;
45 OptionsRegistry() {
46 foldCompact = false;
47 fold = false;
48 }
49};
50
51struct OptionSetRegistry : public OptionSet<OptionsRegistry> {
52 OptionSetRegistry() {
53 DefineProperty("fold.compact", &OptionsRegistry::foldCompact);
54 DefineProperty("fold", &OptionsRegistry::fold);
55 DefineWordListSets(RegistryWordListDesc);
56 }
57};
58
59class LexerRegistry : public DefaultLexer {
60 OptionsRegistry options;
61 OptionSetRegistry optSetRegistry;
62
63 static bool IsStringState(int state) {
64 return (state == SCE_REG_VALUENAME || state == SCE_REG_STRING);
65 }
66
67 static bool IsKeyPathState(int state) {
68 return (state == SCE_REG_ADDEDKEY || state == SCE_REG_DELETEDKEY);
69 }
70
71 static bool AtValueType(LexAccessor &styler, Sci_Position start) {
72 Sci_Position i = 0;
73 while (i < 10) {
74 i++;
75 char curr = styler.SafeGetCharAt(start+i, '\0');
76 if (curr == ':') {
77 return true;
78 } else if (!curr) {
79 return false;
80 }
81 }
82 return false;
83 }
84
85 static bool IsNextNonWhitespace(LexAccessor &styler, Sci_Position start, char ch) {
86 Sci_Position i = 0;
87 while (i < 100) {
88 i++;
89 char curr = styler.SafeGetCharAt(start+i, '\0');
90 char next = styler.SafeGetCharAt(start+i+1, '\0');
91 bool atEOL = (curr == '\r' && next != '\n') || (curr == '\n');
92 if (curr == ch) {
93 return true;
94 } else if (!isspacechar(curr) || atEOL) {
95 return false;
96 }
97 }
98 return false;
99 }
100
101 // Looks for the equal sign at the end of the string
102 static bool AtValueName(LexAccessor &styler, Sci_Position start) {
103 bool atEOL = false;
104 Sci_Position i = 0;
105 bool escaped = false;
106 while (!atEOL) {
107 i++;
108 char curr = styler.SafeGetCharAt(start+i, '\0');
109 char next = styler.SafeGetCharAt(start+i+1, '\0');
110 atEOL = (curr == '\r' && next != '\n') || (curr == '\n');
111 if (escaped) {
112 escaped = false;
113 continue;
114 }
115 escaped = curr == '\\';
116 if (curr == '"') {
117 return IsNextNonWhitespace(styler, start+i, '=');
118 } else if (!curr) {
119 return false;
120 }
121 }
122 return false;
123 }
124
125 static bool AtKeyPathEnd(LexAccessor &styler, Sci_Position start) {
126 bool atEOL = false;
127 Sci_Position i = 0;
128 while (!atEOL) {
129 i++;
130 char curr = styler.SafeGetCharAt(start+i, '\0');
131 char next = styler.SafeGetCharAt(start+i+1, '\0');
132 atEOL = (curr == '\r' && next != '\n') || (curr == '\n');
133 if (curr == ']' || !curr) {
134 // There's still at least one or more square brackets ahead
135 return false;
136 }
137 }
138 return true;
139 }
140
141 static bool AtGUID(LexAccessor &styler, Sci_Position start) {
142 int count = 8;
143 int portion = 0;
144 int offset = 1;
145 char digit = '\0';
146 while (portion < 5) {
147 int i = 0;
148 while (i < count) {
149 digit = styler.SafeGetCharAt(start+offset);
150 if (!(isxdigit(digit) || digit == '-')) {
151 return false;
152 }
153 offset++;
154 i++;
155 }
156 portion++;
157 count = (portion == 4) ? 13 : 5;
158 }
159 digit = styler.SafeGetCharAt(start+offset);
160 if (digit == '}') {
161 return true;
162 } else {
163 return false;
164 }
165 }
166
167public:
168 LexerRegistry() : DefaultLexer("registry", SCLEX_REGISTRY) {}
169 virtual ~LexerRegistry() {}
170 int SCI_METHOD Version() const override {
171 return lvRelease5;
172 }
173 void SCI_METHOD Release() override {
174 delete this;
175 }
176 const char *SCI_METHOD PropertyNames() override {
177 return optSetRegistry.PropertyNames();
178 }
179 int SCI_METHOD PropertyType(const char *name) override {
180 return optSetRegistry.PropertyType(name);
181 }
182 const char *SCI_METHOD DescribeProperty(const char *name) override {
183 return optSetRegistry.DescribeProperty(name);
184 }
185 Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override {
186 if (optSetRegistry.PropertySet(&options, key, val)) {
187 return 0;
188 }
189 return -1;
190 }
191 const char * SCI_METHOD PropertyGet(const char *key) override {
192 return optSetRegistry.PropertyGet(key);
193 }
194
195 Sci_Position SCI_METHOD WordListSet(int, const char *) override {
196 return -1;
197 }
198 void *SCI_METHOD PrivateCall(int, void *) override {
199 return 0;
200 }
201 static ILexer5 *LexerFactoryRegistry() {
202 return new LexerRegistry;
203 }
204 const char *SCI_METHOD DescribeWordListSets() override {
205 return optSetRegistry.DescribeWordListSets();
206 }
207 void SCI_METHOD Lex(Sci_PositionU startPos,
208 Sci_Position length,
209 int initStyle,
210 IDocument *pAccess) override;
211 void SCI_METHOD Fold(Sci_PositionU startPos,
212 Sci_Position length,
213 int initStyle,
214 IDocument *pAccess) override;
215};
216
217void SCI_METHOD LexerRegistry::Lex(Sci_PositionU startPos,
218 Sci_Position length,
219 int initStyle,
220 IDocument *pAccess) {
221 int beforeGUID = SCE_REG_DEFAULT;
222 int beforeEscape = SCE_REG_DEFAULT;
223 CharacterSet setOperators = CharacterSet(CharacterSet::setNone, "-,.=:\\@()");
224 LexAccessor styler(pAccess);
225 StyleContext context(startPos, length, initStyle, styler);
226 bool highlight = true;
227 bool afterEqualSign = false;
228 while (context.More()) {
229 if (context.atLineStart) {
230 Sci_Position currPos = static_cast<Sci_Position>(context.currentPos);
231 bool continued = styler[currPos-3] == '\\';
232 highlight = continued ? true : false;
233 }
234 switch (context.state) {
235 case SCE_REG_COMMENT:
236 if (context.atLineEnd) {
237 context.SetState(SCE_REG_DEFAULT);
238 }
239 break;
240 case SCE_REG_VALUENAME:
241 case SCE_REG_STRING: {
242 Sci_Position currPos = static_cast<Sci_Position>(context.currentPos);
243 if (context.ch == '"') {
244 context.ForwardSetState(SCE_REG_DEFAULT);
245 } else if (context.ch == '\\') {
246 beforeEscape = context.state;
247 context.SetState(SCE_REG_ESCAPED);
248 context.Forward();
249 } else if (context.ch == '{') {
250 if (AtGUID(styler, currPos)) {
251 beforeGUID = context.state;
252 context.SetState(SCE_REG_STRING_GUID);
253 }
254 }
255 if (context.state == SCE_REG_STRING &&
256 context.ch == '%' &&
257 (isdigit(context.chNext) || context.chNext == '*')) {
258 context.SetState(SCE_REG_PARAMETER);
259 }
260 }
261 break;
262 case SCE_REG_PARAMETER:
263 context.ForwardSetState(SCE_REG_STRING);
264 if (context.ch == '"') {
265 context.ForwardSetState(SCE_REG_DEFAULT);
266 }
267 break;
268 case SCE_REG_VALUETYPE:
269 if (context.ch == ':') {
270 context.SetState(SCE_REG_DEFAULT);
271 afterEqualSign = false;
272 }
273 break;
274 case SCE_REG_HEXDIGIT:
275 case SCE_REG_OPERATOR:
276 context.SetState(SCE_REG_DEFAULT);
277 break;
278 case SCE_REG_DELETEDKEY:
279 case SCE_REG_ADDEDKEY: {
280 Sci_Position currPos = static_cast<Sci_Position>(context.currentPos);
281 if (context.ch == ']' && AtKeyPathEnd(styler, currPos)) {
282 context.ForwardSetState(SCE_REG_DEFAULT);
283 } else if (context.ch == '{') {
284 if (AtGUID(styler, currPos)) {
285 beforeGUID = context.state;
286 context.SetState(SCE_REG_KEYPATH_GUID);
287 }
288 }
289 }
290 break;
291 case SCE_REG_ESCAPED:
292 if (context.ch == '"') {
293 context.SetState(beforeEscape);
294 context.ForwardSetState(SCE_REG_DEFAULT);
295 } else if (context.ch == '\\') {
296 context.Forward();
297 } else {
298 context.SetState(beforeEscape);
299 beforeEscape = SCE_REG_DEFAULT;
300 }
301 break;
302 case SCE_REG_STRING_GUID:
303 case SCE_REG_KEYPATH_GUID: {
304 if (context.ch == '}') {
305 context.ForwardSetState(beforeGUID);
306 beforeGUID = SCE_REG_DEFAULT;
307 }
308 Sci_Position currPos = static_cast<Sci_Position>(context.currentPos);
309 if (context.ch == '"' && IsStringState(context.state)) {
310 context.ForwardSetState(SCE_REG_DEFAULT);
311 } else if (context.ch == ']' &&
312 AtKeyPathEnd(styler, currPos) &&
313 IsKeyPathState(context.state)) {
314 context.ForwardSetState(SCE_REG_DEFAULT);
315 } else if (context.ch == '\\' && IsStringState(context.state)) {
316 beforeEscape = context.state;
317 context.SetState(SCE_REG_ESCAPED);
318 context.Forward();
319 }
320 }
321 break;
322 }
323 // Determine if a new state should be entered.
324 if (context.state == SCE_REG_DEFAULT) {
325 Sci_Position currPos = static_cast<Sci_Position>(context.currentPos);
326 if (context.ch == ';') {
327 context.SetState(SCE_REG_COMMENT);
328 } else if (context.ch == '"') {
329 if (AtValueName(styler, currPos)) {
330 context.SetState(SCE_REG_VALUENAME);
331 } else {
332 context.SetState(SCE_REG_STRING);
333 }
334 } else if (context.ch == '[') {
335 if (IsNextNonWhitespace(styler, currPos, '-')) {
336 context.SetState(SCE_REG_DELETEDKEY);
337 } else {
338 context.SetState(SCE_REG_ADDEDKEY);
339 }
340 } else if (context.ch == '=') {
341 afterEqualSign = true;
342 highlight = true;
343 } else if (afterEqualSign) {
344 bool wordStart = isalpha(context.ch) && !isalpha(context.chPrev);
345 if (wordStart && AtValueType(styler, currPos)) {
346 context.SetState(SCE_REG_VALUETYPE);
347 }
348 } else if (isxdigit(context.ch) && highlight) {
349 context.SetState(SCE_REG_HEXDIGIT);
350 }
351 highlight = (context.ch == '@') ? true : highlight;
352 if (setOperators.Contains(context.ch) && highlight) {
353 context.SetState(SCE_REG_OPERATOR);
354 }
355 }
356 context.Forward();
357 }
358 context.Complete();
359}
360
361// Folding similar to that of FoldPropsDoc in LexOthers
362void SCI_METHOD LexerRegistry::Fold(Sci_PositionU startPos,
363 Sci_Position length,
364 int,
365 IDocument *pAccess) {
366 if (!options.fold) {
367 return;
368 }
369 LexAccessor styler(pAccess);
370 Sci_Position currLine = styler.GetLine(startPos);
371 int visibleChars = 0;
372 Sci_PositionU endPos = startPos + length;
373 bool atKeyPath = false;
374 for (Sci_PositionU i = startPos; i < endPos; i++) {
375 atKeyPath = IsKeyPathState(styler.StyleAt(i)) ? true : atKeyPath;
376 char curr = styler.SafeGetCharAt(i);
377 char next = styler.SafeGetCharAt(i+1);
378 bool atEOL = (curr == '\r' && next != '\n') || (curr == '\n');
379 if (atEOL || i == (endPos-1)) {
380 int level = SC_FOLDLEVELBASE;
381 if (currLine > 0) {
382 int prevLevel = styler.LevelAt(currLine-1);
383 if (prevLevel & SC_FOLDLEVELHEADERFLAG) {
384 level += 1;
385 } else {
386 level = prevLevel;
387 }
388 }
389 if (!visibleChars && options.foldCompact) {
390 level |= SC_FOLDLEVELWHITEFLAG;
391 } else if (atKeyPath) {
392 level = SC_FOLDLEVELBASE | SC_FOLDLEVELHEADERFLAG;
393 }
394 if (level != styler.LevelAt(currLine)) {
395 styler.SetLevel(currLine, level);
396 }
397 currLine++;
398 visibleChars = 0;
399 atKeyPath = false;
400 }
401 if (!isspacechar(curr)) {
402 visibleChars++;
403 }
404 }
405
406 // Make the folding reach the last line in the file
407 int level = SC_FOLDLEVELBASE;
408 if (currLine > 0) {
409 int prevLevel = styler.LevelAt(currLine-1);
410 if (prevLevel & SC_FOLDLEVELHEADERFLAG) {
411 level += 1;
412 } else {
413 level = prevLevel;
414 }
415 }
416 styler.SetLevel(currLine, level);
417}
418
419LexerModule lmRegistry(SCLEX_REGISTRY,
420 LexerRegistry::LexerFactoryRegistry,
421 "registry",
422 RegistryWordListDesc);
423
424