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