1 // Scintilla source code edit control
2 /** @file LexSAS.cxx
3 ** Lexer for SAS
4 **/
5 // Author: Luke Rasmussen (luke.rasmussen@gmail.com)
6 //
7 // The License.txt file describes the conditions under which this software may
8 // be distributed.
9 //
10 // Developed as part of the StatTag project at Northwestern University Feinberg
11 // School of Medicine with funding from Northwestern University Clinical and
12 // Translational Sciences Institute through CTSA grant UL1TR001422. This work
13 // has not been reviewed or endorsed by NCATS or the NIH.
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
ColouriseSASDoc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * keywordlists[],Accessor & styler)35 static void ColouriseSASDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *keywordlists[],
36 Accessor &styler) {
37
38 WordList &keywords = *keywordlists[0];
39 WordList &blockKeywords = *keywordlists[1];
40 WordList &functionKeywords = *keywordlists[2];
41 WordList &statements = *keywordlists[3];
42
43 CharacterSet setCouldBePostOp(CharacterSet::setNone, "+-");
44 CharacterSet setMacroStart(CharacterSet::setNone, "%");
45 CharacterSet setWordStart(CharacterSet::setAlpha, "_", 0x80, true);
46 CharacterSet setWord(CharacterSet::setAlphaNum, "._", 0x80, true);
47
48 StyleContext sc(startPos, length, initStyle, styler);
49 bool lineHasNonCommentChar = false;
50 for (; sc.More(); sc.Forward()) {
51 if (sc.atLineStart) {
52 lineHasNonCommentChar = false;
53 }
54
55 // Determine if the current state should terminate.
56 switch (sc.state) {
57 case SCE_SAS_OPERATOR:
58 sc.SetState(SCE_SAS_DEFAULT);
59 break;
60 case SCE_SAS_NUMBER:
61 // We accept almost anything because of hex. and number suffixes
62 if (!setWord.Contains(sc.ch)) {
63 sc.SetState(SCE_SAS_DEFAULT);
64 }
65 break;
66 case SCE_SAS_MACRO:
67 if (!setWord.Contains(sc.ch) || (sc.ch == '.')) {
68 char s[1000];
69 sc.GetCurrentLowered(s, sizeof(s));
70 if (keywords.InList(s)) {
71 sc.ChangeState(SCE_SAS_MACRO_KEYWORD);
72 }
73 else if (blockKeywords.InList(s)) {
74 sc.ChangeState(SCE_SAS_BLOCK_KEYWORD);
75 }
76 else if (functionKeywords.InList(s)) {
77 sc.ChangeState(SCE_SAS_MACRO_FUNCTION);
78 }
79 sc.SetState(SCE_SAS_DEFAULT);
80 }
81 break;
82 case SCE_SAS_IDENTIFIER:
83 if (!setWord.Contains(sc.ch) || (sc.ch == '.')) {
84 char s[1000];
85 sc.GetCurrentLowered(s, sizeof(s));
86 if (statements.InList(s)) {
87 sc.ChangeState(SCE_SAS_STATEMENT);
88 }
89 else if(blockKeywords.InList(s)) {
90 sc.ChangeState(SCE_SAS_BLOCK_KEYWORD);
91 }
92 sc.SetState(SCE_SAS_DEFAULT);
93 }
94 break;
95 case SCE_SAS_COMMENTBLOCK:
96 if (sc.Match('*', '/')) {
97 sc.Forward();
98 sc.ForwardSetState(SCE_SAS_DEFAULT);
99 }
100 break;
101 case SCE_SAS_COMMENT:
102 case SCE_SAS_COMMENTLINE:
103 if (sc.Match(';')) {
104 sc.Forward();
105 sc.SetState(SCE_SAS_DEFAULT);
106 }
107 break;
108 case SCE_SAS_STRING:
109 if (sc.ch == '\"') {
110 sc.ForwardSetState(SCE_SAS_DEFAULT);
111 }
112 break;
113 }
114
115 // Determine if a new state should be entered.
116 if (sc.state == SCE_SAS_DEFAULT) {
117 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
118 lineHasNonCommentChar = true;
119 sc.SetState(SCE_SAS_NUMBER);
120 }
121 else if (setWordStart.Contains(sc.ch)) {
122 lineHasNonCommentChar = true;
123 sc.SetState(SCE_SAS_IDENTIFIER);
124 }
125 else if (sc.Match('*') && !lineHasNonCommentChar) {
126 sc.SetState(SCE_SAS_COMMENT);
127 }
128 else if (sc.Match('/', '*')) {
129 sc.SetState(SCE_SAS_COMMENTBLOCK);
130 sc.Forward(); // Eat the * so it isn't used for the end of the comment
131 }
132 else if (sc.Match('/', '/')) {
133 sc.SetState(SCE_SAS_COMMENTLINE);
134 }
135 else if (sc.ch == '\"') {
136 lineHasNonCommentChar = true;
137 sc.SetState(SCE_SAS_STRING);
138 }
139 else if (setMacroStart.Contains(sc.ch)) {
140 lineHasNonCommentChar = true;
141 sc.SetState(SCE_SAS_MACRO);
142 }
143 else if (isoperator(static_cast<char>(sc.ch))) {
144 lineHasNonCommentChar = true;
145 sc.SetState(SCE_SAS_OPERATOR);
146 }
147 }
148 }
149
150 sc.Complete();
151 }
152
153 // Store both the current line's fold level and the next lines in the
154 // level store to make it easy to pick up with each increment
155 // and to make it possible to fiddle the current level for "} else {".
FoldSASDoc(Sci_PositionU startPos,Sci_Position length,int,WordList * [],Accessor & styler)156 static void FoldSASDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *[],
157 Accessor &styler) {
158 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
159 bool foldAtElse = styler.GetPropertyInt("fold.at.else", 0) != 0;
160 Sci_PositionU endPos = startPos + length;
161 int visibleChars = 0;
162 Sci_Position lineCurrent = styler.GetLine(startPos);
163 int levelCurrent = SC_FOLDLEVELBASE;
164 if (lineCurrent > 0)
165 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
166 int levelMinCurrent = levelCurrent;
167 int levelNext = levelCurrent;
168 char chNext = styler[startPos];
169 int styleNext = styler.StyleAt(startPos);
170 for (Sci_PositionU i = startPos; i < endPos; i++) {
171 char ch = chNext;
172 chNext = styler.SafeGetCharAt(i + 1);
173 int style = styleNext;
174 styleNext = styler.StyleAt(i + 1);
175 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
176 if (style == SCE_R_OPERATOR) {
177 if (ch == '{') {
178 // Measure the minimum before a '{' to allow
179 // folding on "} else {"
180 if (levelMinCurrent > levelNext) {
181 levelMinCurrent = levelNext;
182 }
183 levelNext++;
184 }
185 else if (ch == '}') {
186 levelNext--;
187 }
188 }
189 if (atEOL) {
190 int levelUse = levelCurrent;
191 if (foldAtElse) {
192 levelUse = levelMinCurrent;
193 }
194 int lev = levelUse | levelNext << 16;
195 if (visibleChars == 0 && foldCompact)
196 lev |= SC_FOLDLEVELWHITEFLAG;
197 if (levelUse < levelNext)
198 lev |= SC_FOLDLEVELHEADERFLAG;
199 if (lev != styler.LevelAt(lineCurrent)) {
200 styler.SetLevel(lineCurrent, lev);
201 }
202 lineCurrent++;
203 levelCurrent = levelNext;
204 levelMinCurrent = levelCurrent;
205 visibleChars = 0;
206 }
207 if (!isspacechar(ch))
208 visibleChars++;
209 }
210 }
211
212
213 static const char * const SASWordLists[] = {
214 "Language Keywords",
215 "Macro Keywords",
216 "Types",
217 0,
218 };
219
220 LexerModule lmSAS(SCLEX_SAS, ColouriseSASDoc, "sas", FoldSASDoc, SASWordLists);
221