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