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