1 // Scintilla source code edit control
2 
3 // File: LexMetapost.cxx - general context conformant metapost coloring scheme
4 // Author: Hans Hagen - PRAGMA ADE - Hasselt NL - www.pragma-ade.com
5 // Version: September 28, 2003
6 // Modified by instanton: July 10, 2007
7 // Folding based on keywordlists[]
8 
9 // Copyright: 1998-2003 by Neil Hodgson <neilh@scintilla.org>
10 // The License.txt file describes the conditions under which this software may be distributed.
11 
12 // This lexer is derived from the one written for the texwork environment (1999++) which in
13 // turn is inspired on texedit (1991++) which finds its roots in wdt (1986).
14 
15 #include <stdlib.h>
16 #include <string.h>
17 #include <stdio.h>
18 #include <stdarg.h>
19 #include <assert.h>
20 #include <ctype.h>
21 
22 #include "ILexer.h"
23 #include "Scintilla.h"
24 #include "SciLexer.h"
25 
26 #include "WordList.h"
27 #include "LexAccessor.h"
28 #include "Accessor.h"
29 #include "StyleContext.h"
30 #include "CharacterSet.h"
31 #include "LexerModule.h"
32 
33 using namespace Scintilla;
34 
35 // val SCE_METAPOST_DEFAULT = 0
36 // val SCE_METAPOST_SPECIAL = 1
37 // val SCE_METAPOST_GROUP = 2
38 // val SCE_METAPOST_SYMBOL = 3
39 // val SCE_METAPOST_COMMAND = 4
40 // val SCE_METAPOST_TEXT = 5
41 
42 // Definitions in SciTEGlobal.properties:
43 //
44 // Metapost Highlighting
45 //
46 // # Default
47 // style.metapost.0=fore:#7F7F00
48 // # Special
49 // style.metapost.1=fore:#007F7F
50 // # Group
51 // style.metapost.2=fore:#880000
52 // # Symbol
53 // style.metapost.3=fore:#7F7F00
54 // # Command
55 // style.metapost.4=fore:#008800
56 // # Text
57 // style.metapost.5=fore:#000000
58 
59 // lexer.tex.comment.process=0
60 
61 // Auxiliary functions:
62 
endOfLine(Accessor & styler,Sci_PositionU i)63 static inline bool endOfLine(Accessor &styler, Sci_PositionU i) {
64 	return
65       (styler[i] == '\n') || ((styler[i] == '\r') && (styler.SafeGetCharAt(i + 1) != '\n')) ;
66 }
67 
isMETAPOSTcomment(int ch)68 static inline bool isMETAPOSTcomment(int ch) {
69 	return
70       (ch == '%') ;
71 }
72 
isMETAPOSTone(int ch)73 static inline bool isMETAPOSTone(int ch) {
74 	return
75       (ch == '[') || (ch == ']') || (ch == '(') || (ch == ')') ||
76       (ch == ':') || (ch == '=') || (ch == '<') || (ch == '>') ||
77       (ch == '{') || (ch == '}') || (ch == '\'') || (ch == '\"') ;
78 }
79 
isMETAPOSTtwo(int ch)80 static inline bool isMETAPOSTtwo(int ch) {
81 	return
82       (ch == ';') || (ch == '$') || (ch == '@') || (ch == '#');
83 }
84 
isMETAPOSTthree(int ch)85 static inline bool isMETAPOSTthree(int ch) {
86 	return
87       (ch == '.') || (ch == '-') || (ch == '+') || (ch == '/') ||
88       (ch == '*') || (ch == ',') || (ch == '|') || (ch == '`') ||
89       (ch == '!') || (ch == '?') || (ch == '^') || (ch == '&') ||
90       (ch == '%') ;
91 }
92 
isMETAPOSTidentifier(int ch)93 static inline bool isMETAPOSTidentifier(int ch) {
94 	return
95       ((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) ||
96       (ch == '_') ;
97 }
98 
isMETAPOSTnumber(int ch)99 static inline bool isMETAPOSTnumber(int ch) {
100 	return
101       (ch >= '0') && (ch <= '9') ;
102 }
103 
isMETAPOSTstring(int ch)104 static inline bool isMETAPOSTstring(int ch) {
105 	return
106       (ch == '\"') ;
107 }
108 
isMETAPOSTcolon(int ch)109 static inline bool isMETAPOSTcolon(int ch) {
110 	return
111 		(ch == ':') ;
112 }
113 
isMETAPOSTequal(int ch)114 static inline bool isMETAPOSTequal(int ch) {
115 	return
116 		(ch == '=') ;
117 }
118 
CheckMETAPOSTInterface(Sci_PositionU startPos,Sci_Position length,Accessor & styler,int defaultInterface)119 static int CheckMETAPOSTInterface(
120     Sci_PositionU startPos,
121     Sci_Position length,
122     Accessor &styler,
123 	int defaultInterface) {
124 
125     char lineBuffer[1024] ;
126 	Sci_PositionU linePos = 0 ;
127 
128 	// some day we can make something lexer.metapost.mapping=(none,0)(metapost,1)(mp,1)(metafun,2)...
129 
130     if (styler.SafeGetCharAt(0) == '%') {
131         for (Sci_PositionU i = 0; i < startPos + length; i++) {
132             lineBuffer[linePos++] = styler.SafeGetCharAt(i) ;
133             if (endOfLine(styler, i) || (linePos >= sizeof(lineBuffer) - 1)) {
134                 lineBuffer[linePos] = '\0';
135 				if (strstr(lineBuffer, "interface=none")) {
136                     return 0 ;
137 				} else if (strstr(lineBuffer, "interface=metapost") || strstr(lineBuffer, "interface=mp")) {
138                     return 1 ;
139 				} else if (strstr(lineBuffer, "interface=metafun")) {
140                     return 2 ;
141 				} else if (styler.SafeGetCharAt(1) == 'D' && strstr(lineBuffer, "%D \\module")) {
142 					// better would be to limit the search to just one line
143 					return 2 ;
144                 } else {
145                     return defaultInterface ;
146                 }
147             }
148 		}
149     }
150 
151     return defaultInterface ;
152 }
153 
ColouriseMETAPOSTDoc(Sci_PositionU startPos,Sci_Position length,int,WordList * keywordlists[],Accessor & styler)154 static void ColouriseMETAPOSTDoc(
155     Sci_PositionU startPos,
156     Sci_Position length,
157     int,
158     WordList *keywordlists[],
159     Accessor &styler) {
160 
161 	styler.StartAt(startPos) ;
162 	styler.StartSegment(startPos) ;
163 
164 	bool processComment   = styler.GetPropertyInt("lexer.metapost.comment.process",   0) == 1 ;
165     int  defaultInterface = styler.GetPropertyInt("lexer.metapost.interface.default", 1) ;
166 
167 	int currentInterface = CheckMETAPOSTInterface(startPos,length,styler,defaultInterface) ;
168 
169 	// 0  no keyword highlighting
170 	// 1  metapost keyword hightlighting
171 	// 2+ metafun keyword hightlighting
172 
173 	int extraInterface = 0 ;
174 
175 	if (currentInterface != 0) {
176 		extraInterface = currentInterface ;
177 	}
178 
179 	WordList &keywords  = *keywordlists[0] ;
180 	WordList &keywords2 = *keywordlists[extraInterface-1] ;
181 
182 	StyleContext sc(startPos, length, SCE_METAPOST_TEXT, styler) ;
183 
184 	char key[100] ;
185 
186     bool inTeX     = false ;
187 	bool inComment = false ;
188 	bool inString  = false ;
189 	bool inClause  = false ;
190 
191 	bool going = sc.More() ; // needed because of a fuzzy end of file state
192 
193 	for (; going; sc.Forward()) {
194 
195 		if (! sc.More()) { going = false ; } // we need to go one behind the end of text
196 
197 		if (inClause) {
198 			sc.SetState(SCE_METAPOST_TEXT) ;
199 			inClause = false ;
200 		}
201 
202 		if (inComment) {
203 			if (sc.atLineEnd) {
204 				sc.SetState(SCE_METAPOST_TEXT) ;
205 				inTeX = false ;
206 				inComment = false ;
207 				inClause = false ;
208 				inString = false ; // not correct but we want to stimulate one-lines
209 			}
210 		} else if (inString) {
211 			if (isMETAPOSTstring(sc.ch)) {
212 				sc.SetState(SCE_METAPOST_SPECIAL) ;
213 				sc.ForwardSetState(SCE_METAPOST_TEXT) ;
214 				inString = false ;
215 			} else if (sc.atLineEnd) {
216 				sc.SetState(SCE_METAPOST_TEXT) ;
217 				inTeX = false ;
218 				inComment = false ;
219 				inClause = false ;
220 				inString = false ; // not correct but we want to stimulate one-lines
221 			}
222 		} else {
223 			if ((! isMETAPOSTidentifier(sc.ch)) && (sc.LengthCurrent() > 0)) {
224 				if (sc.state == SCE_METAPOST_COMMAND) {
225 					sc.GetCurrent(key, sizeof(key)) ;
226 					if ((strcmp(key,"btex") == 0) || (strcmp(key,"verbatimtex") == 0)) {
227     					sc.ChangeState(SCE_METAPOST_GROUP) ;
228 						inTeX = true ;
229 					} else if (inTeX) {
230 						if (strcmp(key,"etex") == 0) {
231 	    					sc.ChangeState(SCE_METAPOST_GROUP) ;
232 							inTeX = false ;
233 						} else {
234 	    					sc.ChangeState(SCE_METAPOST_TEXT) ;
235 						}
236 					} else {
237 						if (keywords && keywords.InList(key)) {
238     						sc.ChangeState(SCE_METAPOST_COMMAND) ;
239 						} else if (keywords2 && keywords2.InList(key)) {
240 							sc.ChangeState(SCE_METAPOST_EXTRA) ;
241 						} else {
242 							sc.ChangeState(SCE_METAPOST_TEXT) ;
243 						}
244 					}
245 				}
246 			}
247 			if (isMETAPOSTcomment(sc.ch)) {
248 				if (! inTeX) {
249 					sc.SetState(SCE_METAPOST_SYMBOL) ;
250 					sc.ForwardSetState(SCE_METAPOST_DEFAULT) ;
251 					inComment = ! processComment ;
252 				} else {
253 					sc.SetState(SCE_METAPOST_TEXT) ;
254 				}
255 			} else if (isMETAPOSTstring(sc.ch)) {
256 				if (! inTeX) {
257 					sc.SetState(SCE_METAPOST_SPECIAL) ;
258 					if (! isMETAPOSTstring(sc.chNext)) {
259 						sc.ForwardSetState(SCE_METAPOST_TEXT) ;
260 					}
261 					inString = true ;
262 				} else {
263 					sc.SetState(SCE_METAPOST_TEXT) ;
264 				}
265 			} else if (isMETAPOSTcolon(sc.ch)) {
266 				if (! inTeX) {
267 					if (! isMETAPOSTequal(sc.chNext)) {
268 						sc.SetState(SCE_METAPOST_COMMAND) ;
269 						inClause = true ;
270 					} else {
271 						sc.SetState(SCE_METAPOST_SPECIAL) ;
272 					}
273 				} else {
274 					sc.SetState(SCE_METAPOST_TEXT) ;
275 				}
276 			} else if (isMETAPOSTone(sc.ch)) {
277 				if (! inTeX) {
278 					sc.SetState(SCE_METAPOST_SPECIAL) ;
279 				} else {
280 					sc.SetState(SCE_METAPOST_TEXT) ;
281 				}
282 			} else if (isMETAPOSTtwo(sc.ch)) {
283 				if (! inTeX) {
284 					sc.SetState(SCE_METAPOST_GROUP) ;
285 				} else {
286 					sc.SetState(SCE_METAPOST_TEXT) ;
287 				}
288 			} else if (isMETAPOSTthree(sc.ch)) {
289 				if (! inTeX) {
290 					sc.SetState(SCE_METAPOST_SYMBOL) ;
291 				} else {
292 					sc.SetState(SCE_METAPOST_TEXT) ;
293 				}
294 			} else if (isMETAPOSTidentifier(sc.ch)) {
295 				if (sc.state != SCE_METAPOST_COMMAND) {
296 					sc.SetState(SCE_METAPOST_TEXT) ;
297 					sc.ChangeState(SCE_METAPOST_COMMAND) ;
298 				}
299 			} else if (isMETAPOSTnumber(sc.ch)) {
300 				// rather redundant since for the moment we don't handle numbers
301 				sc.SetState(SCE_METAPOST_TEXT) ;
302 			} else if (sc.atLineEnd) {
303 				sc.SetState(SCE_METAPOST_TEXT) ;
304 				inTeX = false ;
305 				inComment = false ;
306 				inClause = false ;
307 				inString = false ;
308 			} else {
309 				sc.SetState(SCE_METAPOST_TEXT) ;
310 			}
311 		}
312 
313 	}
314 
315 	sc.Complete();
316 
317 }
318 
319 // Hooks info the system:
320 
321 static const char * const metapostWordListDesc[] = {
322 	"MetaPost",
323 	"MetaFun",
324 	0
325 } ;
326 
classifyFoldPointMetapost(const char * s,WordList * keywordlists[])327 static int classifyFoldPointMetapost(const char* s,WordList *keywordlists[]) {
328 	WordList& keywordsStart=*keywordlists[3];
329 	WordList& keywordsStop1=*keywordlists[4];
330 
331 	if (keywordsStart.InList(s)) {return 1;}
332 	else if (keywordsStop1.InList(s)) {return -1;}
333 	return 0;
334 
335 }
336 
ParseMetapostWord(Sci_PositionU pos,Accessor & styler,char * word)337 static int ParseMetapostWord(Sci_PositionU pos, Accessor &styler, char *word)
338 {
339   int length=0;
340   char ch=styler.SafeGetCharAt(pos);
341   *word=0;
342 
343   while(isMETAPOSTidentifier(ch) && isalpha(ch) && length<100){
344           word[length]=ch;
345           length++;
346           ch=styler.SafeGetCharAt(pos+length);
347   }
348   word[length]=0;
349   return length;
350 }
351 
FoldMetapostDoc(Sci_PositionU startPos,Sci_Position length,int,WordList * keywordlists[],Accessor & styler)352 static void FoldMetapostDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *keywordlists[], Accessor &styler)
353 {
354 	bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
355 	Sci_PositionU endPos = startPos+length;
356 	int visibleChars=0;
357 	Sci_Position lineCurrent=styler.GetLine(startPos);
358 	int levelPrev=styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
359 	int levelCurrent=levelPrev;
360 	char chNext=styler[startPos];
361 
362 	char buffer[100]="";
363 
364 	for (Sci_PositionU i=startPos; i < endPos; i++) {
365 		char ch=chNext;
366 		chNext=styler.SafeGetCharAt(i+1);
367 		char chPrev=styler.SafeGetCharAt(i-1);
368 		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
369 
370 		if(i==0 || chPrev == '\r' || chPrev=='\n'|| chPrev==' '|| chPrev=='(' || chPrev=='$')
371 		{
372             ParseMetapostWord(i, styler, buffer);
373 			levelCurrent += classifyFoldPointMetapost(buffer,keywordlists);
374 		}
375 
376 		if (atEOL) {
377 			int lev = levelPrev;
378 			if (visibleChars == 0 && foldCompact)
379 				lev |= SC_FOLDLEVELWHITEFLAG;
380 			if ((levelCurrent > levelPrev) && (visibleChars > 0))
381 				lev |= SC_FOLDLEVELHEADERFLAG;
382 			if (lev != styler.LevelAt(lineCurrent)) {
383 				styler.SetLevel(lineCurrent, lev);
384 			}
385 			lineCurrent++;
386 			levelPrev = levelCurrent;
387 			visibleChars = 0;
388 		}
389 
390 		if (!isspacechar(ch))
391 			visibleChars++;
392 	}
393 	// Fill in the real level of the next line, keeping the current flags as they will be filled in later
394 	int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
395 	styler.SetLevel(lineCurrent, levelPrev | flagsNext);
396 
397 }
398 
399 
400 LexerModule lmMETAPOST(SCLEX_METAPOST, ColouriseMETAPOSTDoc, "metapost", FoldMetapostDoc, metapostWordListDesc);
401