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 | |
35 | using namespace Scintilla; |
36 | using namespace Lexilla; |
37 | |
38 | static const char *const RegistryWordListDesc[] = { |
39 | 0 |
40 | }; |
41 | |
42 | struct OptionsRegistry { |
43 | bool foldCompact; |
44 | bool fold; |
45 | OptionsRegistry() { |
46 | foldCompact = false; |
47 | fold = false; |
48 | } |
49 | }; |
50 | |
51 | struct OptionSetRegistry : public OptionSet<OptionsRegistry> { |
52 | OptionSetRegistry() { |
53 | DefineProperty("fold.compact" , &OptionsRegistry::foldCompact); |
54 | DefineProperty("fold" , &OptionsRegistry::fold); |
55 | DefineWordListSets(RegistryWordListDesc); |
56 | } |
57 | }; |
58 | |
59 | class 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 | |
167 | public: |
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 | |
217 | void 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 |
362 | void 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 | |
419 | LexerModule lmRegistry(SCLEX_REGISTRY, |
420 | LexerRegistry::LexerFactoryRegistry, |
421 | "registry" , |
422 | RegistryWordListDesc); |
423 | |
424 | |