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