1 // Scintilla source code edit control
2 /** @file LexRebol.cxx
3  ** Lexer for REBOL.
4  ** Written by Pascal Hurni, inspired from LexLua by Paul Winwood & Marcos E. Wurzius & Philippe Lhoste
5  **
6  ** History:
7  **		2005-04-07	First release.
8  **		2005-04-10	Closing parens and brackets go now in default style
9  **					String and comment nesting should be more safe
10  **/
11 // Copyright 2005 by Pascal Hurni <pascal_hurni@fastmail.fm>
12 // The License.txt file describes the conditions under which this software may be distributed.
13 
14 #include <stdlib.h>
15 #include <string.h>
16 #include <ctype.h>
17 #include <stdio.h>
18 #include <stdarg.h>
19 
20 #include "Platform.h"
21 
22 #include "PropSet.h"
23 #include "Accessor.h"
24 #include "KeyWords.h"
25 #include "Scintilla.h"
26 #include "SciLexer.h"
27 #include "StyleContext.h"
28 
29 
IsAWordChar(const int ch)30 static inline bool IsAWordChar(const int ch) {
31 	return (isalnum(ch) || ch == '?' || ch == '!' || ch == '.' || ch == '\'' || ch == '+' || ch == '-' || ch == '*' || ch == '&' || ch == '|' || ch == '=' || ch == '_' || ch == '~');
32 }
33 
IsAWordStart(const int ch,const int ch2)34 static inline bool IsAWordStart(const int ch, const int ch2) {
35 	return ((ch == '+' || ch == '-' || ch == '.') && !isdigit(ch2)) ||
36 		(isalpha(ch) || ch == '?' || ch == '!' || ch == '\'' || ch == '*' || ch == '&' || ch == '|' || ch == '=' || ch == '_' || ch == '~');
37 }
38 
IsAnOperator(const int ch,const int ch2,const int ch3)39 static inline bool IsAnOperator(const int ch, const int ch2, const int ch3) {
40 	// One char operators
41 	if (IsASpaceOrTab(ch2)) {
42 		return ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '<' || ch == '>' || ch == '=' || ch == '?';
43 	}
44 
45 	// Two char operators
46 	if (IsASpaceOrTab(ch3)) {
47 		return (ch == '*' && ch2 == '*') ||
48 			   (ch == '/' && ch2 == '/') ||
49 			   (ch == '<' && (ch2 == '=' || ch2 == '>')) ||
50 			   (ch == '>' && ch2 == '=') ||
51 			   (ch == '=' && (ch2 == '=' || ch2 == '?')) ||
52 			   (ch == '?' && ch2 == '?');
53 	}
54 
55 	return false;
56 }
57 
IsBinaryStart(const int ch,const int ch2,const int ch3,const int ch4)58 static inline bool IsBinaryStart(const int ch, const int ch2, const int ch3, const int ch4) {
59 	return (ch == '#' && ch2 == '{') ||
60 		   (IsADigit(ch) && ch2 == '#' && ch3 == '{' ) ||
61 		   (IsADigit(ch) && IsADigit(ch2) && ch3 == '#' && ch4 == '{' );
62 }
63 
64 
ColouriseRebolDoc(unsigned int startPos,int length,int initStyle,WordList * keywordlists[],Accessor & styler)65 static void ColouriseRebolDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[], Accessor &styler) {
66 
67 	WordList &keywords = *keywordlists[0];
68 	WordList &keywords2 = *keywordlists[1];
69 	WordList &keywords3 = *keywordlists[2];
70 	WordList &keywords4 = *keywordlists[3];
71 	WordList &keywords5 = *keywordlists[4];
72 	WordList &keywords6 = *keywordlists[5];
73 	WordList &keywords7 = *keywordlists[6];
74 	WordList &keywords8 = *keywordlists[7];
75 
76 	int currentLine = styler.GetLine(startPos);
77 	// Initialize the braced string {.. { ... } ..} nesting level, if we are inside such a string.
78 	int stringLevel = 0;
79 	if (initStyle == SCE_REBOL_BRACEDSTRING || initStyle == SCE_REBOL_COMMENTBLOCK) {
80 		stringLevel = styler.GetLineState(currentLine - 1);
81 	}
82 
83 	bool blockComment = initStyle == SCE_REBOL_COMMENTBLOCK;
84 	int dotCount = 0;
85 
86 	// Do not leak onto next line
87 	if (initStyle == SCE_REBOL_COMMENTLINE) {
88 		initStyle = SCE_REBOL_DEFAULT;
89 	}
90 
91 	StyleContext sc(startPos, length, initStyle, styler);
92 	if (startPos == 0) {
93 		sc.SetState(SCE_REBOL_PREFACE);
94 	}
95 	for (; sc.More(); sc.Forward()) {
96 
97 		//--- What to do at line end ?
98 		if (sc.atLineEnd) {
99 			// Can be either inside a {} string or simply at eol
100 			if (sc.state != SCE_REBOL_BRACEDSTRING && sc.state != SCE_REBOL_COMMENTBLOCK &&
101 				sc.state != SCE_REBOL_BINARY && sc.state != SCE_REBOL_PREFACE)
102 				sc.SetState(SCE_REBOL_DEFAULT);
103 
104 			// Update the line state, so it can be seen by next line
105 			currentLine = styler.GetLine(sc.currentPos);
106 			switch (sc.state) {
107 			case SCE_REBOL_BRACEDSTRING:
108 			case SCE_REBOL_COMMENTBLOCK:
109 				// Inside a braced string, we set the line state
110 				styler.SetLineState(currentLine, stringLevel);
111 				break;
112 			default:
113 				// Reset the line state
114 				styler.SetLineState(currentLine, 0);
115 				break;
116 			}
117 
118 			// continue with next char
119 			continue;
120 		}
121 
122 		//--- What to do on white-space ?
123 		if (IsASpaceOrTab(sc.ch))
124 		{
125 			// Return to default if any of these states
126 			if (sc.state == SCE_REBOL_OPERATOR || sc.state == SCE_REBOL_CHARACTER ||
127 				sc.state == SCE_REBOL_NUMBER || sc.state == SCE_REBOL_PAIR ||
128 				sc.state == SCE_REBOL_TUPLE || sc.state == SCE_REBOL_FILE ||
129 				sc.state == SCE_REBOL_DATE || sc.state == SCE_REBOL_TIME ||
130 				sc.state == SCE_REBOL_MONEY || sc.state == SCE_REBOL_ISSUE ||
131 				sc.state == SCE_REBOL_URL || sc.state == SCE_REBOL_EMAIL) {
132 				sc.SetState(SCE_REBOL_DEFAULT);
133 			}
134 		}
135 
136 		//--- Specialize state ?
137 		// URL, Email look like identifier
138 		if (sc.state == SCE_REBOL_IDENTIFIER)
139 		{
140 			if (sc.ch == ':' && !IsASpace(sc.chNext)) {
141 				sc.ChangeState(SCE_REBOL_URL);
142 			} else if (sc.ch == '@') {
143 				sc.ChangeState(SCE_REBOL_EMAIL);
144 			} else if (sc.ch == '$') {
145 				sc.ChangeState(SCE_REBOL_MONEY);
146 			}
147 		}
148 		// Words look like identifiers
149 		if (sc.state == SCE_REBOL_IDENTIFIER || (sc.state >= SCE_REBOL_WORD && sc.state <= SCE_REBOL_WORD8)) {
150 			// Keywords ?
151 			if (!IsAWordChar(sc.ch) || sc.Match('/')) {
152 				char s[100];
153 				sc.GetCurrentLowered(s, sizeof(s));
154 				blockComment = strcmp(s, "comment") == 0;
155 				if (keywords8.InList(s)) {
156 					sc.ChangeState(SCE_REBOL_WORD8);
157 				} else if (keywords7.InList(s)) {
158 					sc.ChangeState(SCE_REBOL_WORD7);
159 				} else if (keywords6.InList(s)) {
160 					sc.ChangeState(SCE_REBOL_WORD6);
161 				} else if (keywords5.InList(s)) {
162 					sc.ChangeState(SCE_REBOL_WORD5);
163 				} else if (keywords4.InList(s)) {
164 					sc.ChangeState(SCE_REBOL_WORD4);
165 				} else if (keywords3.InList(s)) {
166 					sc.ChangeState(SCE_REBOL_WORD3);
167 				} else if (keywords2.InList(s)) {
168 					sc.ChangeState(SCE_REBOL_WORD2);
169 				} else if (keywords.InList(s)) {
170 					sc.ChangeState(SCE_REBOL_WORD);
171 				}
172 				// Keep same style if there are refinements
173 				if (!sc.Match('/')) {
174 					sc.SetState(SCE_REBOL_DEFAULT);
175 				}
176 			}
177 		// special numbers
178 		} else if (sc.state == SCE_REBOL_NUMBER) {
179 			switch (sc.ch) {
180 			case 'x':	sc.ChangeState(SCE_REBOL_PAIR);
181 						break;
182 			case ':':	sc.ChangeState(SCE_REBOL_TIME);
183 						break;
184 			case '-':
185 			case '/':	sc.ChangeState(SCE_REBOL_DATE);
186 						break;
187 			case '.':	if (++dotCount >= 2) sc.ChangeState(SCE_REBOL_TUPLE);
188 						break;
189 			}
190 		}
191 
192 		//--- Determine if the current state should terminate
193 		if (sc.state == SCE_REBOL_QUOTEDSTRING || sc.state == SCE_REBOL_CHARACTER) {
194 			if (sc.ch == '^' && sc.chNext == '\"') {
195 				sc.Forward();
196 			} else if (sc.ch == '\"') {
197 				sc.ForwardSetState(SCE_REBOL_DEFAULT);
198 			}
199 		} else if (sc.state == SCE_REBOL_BRACEDSTRING || sc.state == SCE_REBOL_COMMENTBLOCK) {
200 			if (sc.ch == '}') {
201 				if (--stringLevel == 0) {
202 					sc.ForwardSetState(SCE_REBOL_DEFAULT);
203 				}
204 			} else if (sc.ch == '{') {
205 				stringLevel++;
206 			}
207 		} else if (sc.state == SCE_REBOL_BINARY) {
208 			if (sc.ch == '}') {
209 				sc.ForwardSetState(SCE_REBOL_DEFAULT);
210 			}
211 		} else if (sc.state == SCE_REBOL_TAG) {
212 			if (sc.ch == '>') {
213 				sc.ForwardSetState(SCE_REBOL_DEFAULT);
214 			}
215 		} else if (sc.state == SCE_REBOL_PREFACE) {
216 			if (sc.MatchIgnoreCase("rebol"))
217 			{
218 				int i;
219 				for (i=5; IsASpaceOrTab(styler.SafeGetCharAt(sc.currentPos+i, 0)); i++);
220 				if (sc.GetRelative(i) == '[')
221 					sc.SetState(SCE_REBOL_DEFAULT);
222 			}
223 		}
224 
225 		//--- Parens and bracket changes to default style when the current is a number
226 		if (sc.state == SCE_REBOL_NUMBER || sc.state == SCE_REBOL_PAIR || sc.state == SCE_REBOL_TUPLE ||
227 			sc.state == SCE_REBOL_MONEY || sc.state == SCE_REBOL_ISSUE || sc.state == SCE_REBOL_EMAIL ||
228 			sc.state == SCE_REBOL_URL || sc.state == SCE_REBOL_DATE || sc.state == SCE_REBOL_TIME) {
229 			if (sc.ch == '(' || sc.ch == '[' || sc.ch == ')' || sc.ch == ']') {
230 				sc.SetState(SCE_REBOL_DEFAULT);
231 			}
232 		}
233 
234 		//--- Determine if a new state should be entered.
235 		if (sc.state == SCE_REBOL_DEFAULT) {
236 			if (IsAnOperator(sc.ch, sc.chNext, sc.GetRelative(2))) {
237 				sc.SetState(SCE_REBOL_OPERATOR);
238 			} else if (IsBinaryStart(sc.ch, sc.chNext, sc.GetRelative(2), sc.GetRelative(3))) {
239 				sc.SetState(SCE_REBOL_BINARY);
240 			} else if (IsAWordStart(sc.ch, sc.chNext)) {
241 				sc.SetState(SCE_REBOL_IDENTIFIER);
242 			} else if (IsADigit(sc.ch) || sc.ch == '+' || sc.ch == '-' || /*Decimal*/ sc.ch == '.' || sc.ch == ',') {
243 				dotCount = 0;
244 				sc.SetState(SCE_REBOL_NUMBER);
245 			} else if (sc.ch == '\"') {
246 				sc.SetState(SCE_REBOL_QUOTEDSTRING);
247 			} else if (sc.ch == '{') {
248 				sc.SetState(blockComment ? SCE_REBOL_COMMENTBLOCK : SCE_REBOL_BRACEDSTRING);
249 				++stringLevel;
250 			} else if (sc.ch == ';') {
251 				sc.SetState(SCE_REBOL_COMMENTLINE);
252 			} else if (sc.ch == '$') {
253 				sc.SetState(SCE_REBOL_MONEY);
254 			} else if (sc.ch == '%') {
255 				sc.SetState(SCE_REBOL_FILE);
256 			} else if (sc.ch == '<') {
257 				sc.SetState(SCE_REBOL_TAG);
258 			} else if (sc.ch == '#' && sc.chNext == '"') {
259 				sc.SetState(SCE_REBOL_CHARACTER);
260 				sc.Forward();
261 			} else if (sc.ch == '#' && sc.chNext != '"' && sc.chNext != '{' ) {
262 				sc.SetState(SCE_REBOL_ISSUE);
263 			}
264 		}
265 	}
266 	sc.Complete();
267 }
268 
269 
FoldRebolDoc(unsigned int startPos,int length,int,WordList * [],Accessor & styler)270 static void FoldRebolDoc(unsigned int startPos, int length, int /* initStyle */, WordList *[],
271                             Accessor &styler) {
272 	unsigned int lengthDoc = startPos + length;
273 	int visibleChars = 0;
274 	int lineCurrent = styler.GetLine(startPos);
275 	int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
276 	int levelCurrent = levelPrev;
277 	char chNext = styler[startPos];
278 	int styleNext = styler.StyleAt(startPos);
279 	for (unsigned int i = startPos; i < lengthDoc; i++) {
280 		char ch = chNext;
281 		chNext = styler.SafeGetCharAt(i + 1);
282 		int style = styleNext;
283 		styleNext = styler.StyleAt(i + 1);
284 		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
285 		if (style == SCE_REBOL_DEFAULT) {
286 			if (ch == '[') {
287 				levelCurrent++;
288 			} else if (ch == ']') {
289 				levelCurrent--;
290 			}
291 		}
292 		if (atEOL) {
293 			int lev = levelPrev;
294 			if (visibleChars == 0)
295 				lev |= SC_FOLDLEVELWHITEFLAG;
296 			if ((levelCurrent > levelPrev) && (visibleChars > 0))
297 				lev |= SC_FOLDLEVELHEADERFLAG;
298 			if (lev != styler.LevelAt(lineCurrent)) {
299 				styler.SetLevel(lineCurrent, lev);
300 			}
301 			lineCurrent++;
302 			levelPrev = levelCurrent;
303 			visibleChars = 0;
304 		}
305 		if (!isspacechar(ch))
306 			visibleChars++;
307 	}
308 	// Fill in the real level of the next line, keeping the current flags as they will be filled in later
309 	int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
310 	styler.SetLevel(lineCurrent, levelPrev | flagsNext);
311 }
312 
313 static const char * const rebolWordListDesc[] = {
314 	"Keywords",
315 	0
316 };
317 
318 LexerModule lmREBOL(SCLEX_REBOL, ColouriseRebolDoc, "rebol", FoldRebolDoc, rebolWordListDesc);
319 
320