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