Source code: org/gjt/sp/jedit/syntax/FortranTokenMarker.java
1 /*
2 * FortranTokenMarker.java - Fortran token marker
3 * by Carl Smotricz
4 * carl@smotricz.com
5 * www.smotricz.com
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21
22 package org.gjt.sp.jedit.syntax;
23
24 import javax.swing.text.Segment;
25
26 /**
27 * Custom TokenMarker for UNISYS's <cite>ASCII FORTRAN 77</cite>.
28 * Characteristics of this dialect are:<ul>
29 * <li>Fixed column format, with<ul>
30 * <li>comment character ( 'C'|'c'|'*' ) in column 1,</li>
31 * <li>labels (numeric) in column 1-5,</li>
32 * <li>continuation character ( any nonblank ) in column 6,</li>
33 * <li>logical end of line after column 72.</li>
34 * </ul></li>
35 * <li>Nonstandard block comment character ( '@' ) in any column,</li>
36 * <li>Some nonstandard functions: <code>BITS</code>, <code>BOOL</code>,
37 * <code>INDEX</code>, <code>TRMLEN</code></li>
38 * </ul>
39 * It should be easy enough to adapt this class for minor variations
40 * in the dialect so long as the format is the classic fixed column format.
41 * As this scanner is highly optimized for the fixed column format, it
42 * is probably not readily adaptable for freeform FORTRAN code.
43 */
44
45 public class FortranTokenMarker extends TokenMarker
46 {
47 // private members
48
49 private final static int MAYBE_KEYWORD_FIRST = Token.INTERNAL_FIRST;
50 private final static int MAYBE_KEYWORD_MORE = 1 + MAYBE_KEYWORD_FIRST;
51 private final static String S_E_P = "START EDIT PAGE";
52
53 private static KeywordMap fortranKeywords;
54 private KeywordMap keywords;
55
56 private int lastOffset;
57
58 /**
59 * Constructor, with a wee bit of initialization.
60 */
61 public FortranTokenMarker()
62 {
63 this.keywords = getKeywords();
64 }
65
66 /**
67 * Implementation of code to mark tokens.
68 */
69 public byte markTokensImpl(byte token, Segment line, int lineIndex)
70 {
71 byte lastLineToken = token;
72
73 // --- Very quick check for empty line
74 if (line.count < 1) return lastLineToken; // EXIT METHOD!
75
76 char[] array = line.array;
77 int offset = line.offset;
78 char c = array[offset];
79
80 // --- Very quick check for 'C' comment line
81 if (c == 'C' || c == 'c' || c == '*')
82 {
83 addToken(line.count, Token.COMMENT1);
84 return lastLineToken; // EXIT METHOD!
85 }
86
87 token = Token.NULL; // context usually ends on line boundary
88 int lineEnd = offset + line.count;
89
90 // --- Check for a label
91 int limit = Math.min(lineEnd, offset + 5);
92 int i;
93 for (i=offset; i<limit; i++)
94 {
95 c = array[i];
96 if (c == '@')
97 {
98 // comment to end of line
99 guardedAddToken(i - offset, token);
100 addToken(lineEnd - i, Token.COMMENT2);
101 return lastLineToken; // EXIT METHOD!
102 }
103 else if (token == Token.NULL && '0' <= c && c <= '9')
104 {
105 // numerics: Label.
106 token = Token.LABEL;
107 }
108 }
109 addToken(limit - offset, token);
110
111 // --- End of line?
112 if (limit == lineEnd) return Token.NULL; // EXIT METHOD!
113
114 // --- Check for line continuation
115 c = array[i];
116 if (c == '@')
117 {
118 // comment to end of line
119 addToken(lineEnd - i, Token.COMMENT2);
120 return Token.NULL; // EXIT METHOD!
121 }
122 else if (c == ' ')
123 {
124 // just a plain old blank
125 addToken(1, Token.NULL);
126 token = Token.NULL;
127 }
128 else
129 {
130 // line continuation: mark it as a label to make it stand out
131 addToken(1, Token.LABEL);
132 token = lastLineToken;
133 }
134
135 // --- End of line?
136 if (lineEnd == offset + 6) return Token.NULL; // EXIT METHOD!
137
138 limit = Math.min(offset + 72, lineEnd);
139 lastOffset = offset + 6;
140
141 // --- Check for "START EDIT PAGE"
142 if (checkStartEditPage(line)) {
143 addToken(limit - lastOffset, Token.LABEL);
144 return Token.NULL; // EXIT METHOD!
145 }
146
147 // --- 'normal' real, honest coding now
148 int i1;
149 for(i = lastOffset; i < limit; i++)
150 {
151 i1 = i + 1;
152 c = array[i];
153
154 if (token == Token.LITERAL1)
155 {
156 // ignore anything but the end of literal
157 if (c == '\'')
158 {
159 addToken(i1 - lastOffset, Token.LITERAL1);
160 token = Token.NULL;
161 lastOffset = i1;
162 }
163 }
164 else if (c == '@')
165 {
166 // comment to end of line
167 guardedAddToken(i - lastOffset, token);
168 addToken(lineEnd - i, Token.COMMENT2);
169 return token; // EXIT METHOD!
170 }
171 else if (token == Token.NULL)
172 {
173 switch (c)
174 {
175 case '\'':
176 guardedAddToken(i - lastOffset, token);
177 token = Token.LITERAL1;
178 lastOffset = i;
179 break;
180 case '+':
181 case '-':
182 case '*':
183 case '/':
184 case '(':
185 case ')':
186 case ',':
187 case ':':
188 case '=':
189 guardedAddToken(i - lastOffset, token);
190 addToken(1, Token.OPERATOR);
191 lastOffset = i1;
192 break;
193 default:
194 if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))
195 {
196 guardedAddToken(i - lastOffset, token);
197 token = MAYBE_KEYWORD_FIRST;
198 lastOffset = i;
199 break;
200 }
201 else
202 {
203 // unknown special char - maintain state
204 }
205 }
206 }
207 else if (token == MAYBE_KEYWORD_FIRST ||
208 token == MAYBE_KEYWORD_MORE)
209 {
210 if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') ||
211 ('0' <= c && c <= '9') || (c == '$'))
212 {
213 token = MAYBE_KEYWORD_MORE;
214 }
215 else
216 {
217 doKeyword(line, i);
218 c = array[i];
219 switch (c)
220 {
221 case '+':
222 case '-':
223 case '*':
224 case '/':
225 case '(':
226 case ')':
227 case ',':
228 case ':':
229 case '=':
230 guardedAddToken(i - lastOffset, token);
231 addToken(1, Token.OPERATOR);
232 lastOffset = i1;
233 break;
234 }
235 token = Token.NULL;
236 }
237 }
238 else
239 {
240 throw new InternalError("Invalid state: " + token);
241 }
242 } // end for
243
244 // --- Finish up the coding part of the line
245 if (token == MAYBE_KEYWORD_FIRST || token == MAYBE_KEYWORD_MORE)
246 {
247 doKeyword(line, i);
248 token = Token.NULL;
249 }
250 else
251 {
252 guardedAddToken(i - lastOffset, token);
253 }
254
255 // --- End of line?
256 if (limit == lineEnd) return token; // EXIT METHOD!
257
258 // --- Anything beyond column 72 is comment
259 guardedAddToken(lineEnd - i, Token.COMMENT2);
260 //
261 return token;
262 }
263
264 private boolean checkStartEditPage(Segment line)
265 {
266 if (line.count < 6+15) return false;
267 int limit = line.offset + Math.min(line.count, 72);
268 int i;
269 for (i=line.offset+6; i<limit-15; i++) if (line.array[i] != ' ') break;
270 if (!SyntaxUtilities.regionMatches(false, line, i, S_E_P)) return false;
271 for (i+=15; i<limit; i++) if (line.array[i] != ' ') return false;
272 return true;
273 }
274
275 /**
276 * Add the latest token to the current list.
277 * Process 'START' as a special case.
278 */
279 private void doKeyword(Segment line, int keywordEnd)
280 {
281 int len = keywordEnd - lastOffset;
282 if (len > 0)
283 {
284 byte id = keywords.lookup(line, lastOffset, len);
285 addToken(len, id);
286 lastOffset = keywordEnd;
287 }
288 }
289
290 /**
291 * Call addToken only if the length of the token is not 0.
292 */
293 private void guardedAddToken(int len, byte token)
294 {
295 if (len > 0) addToken(len, token);
296 }
297
298 /**
299 * Return the keyword map.
300 * It's lazily initialized on the first call.
301 */
302 public static KeywordMap getKeywords()
303 {
304 if (fortranKeywords == null)
305 {
306 fortranKeywords = new KeywordMap(false);
307
308 // === Commands ===
309 fortranKeywords.add("CALL", Token.KEYWORD1);
310 fortranKeywords.add("CLOSE", Token.KEYWORD1);
311 fortranKeywords.add("CONTINUE", Token.KEYWORD1);
312 fortranKeywords.add("DO", Token.KEYWORD1);
313 fortranKeywords.add("ELSE", Token.KEYWORD1);
314 fortranKeywords.add("ELSEIF", Token.KEYWORD1);
315 fortranKeywords.add("ENDIF", Token.KEYWORD1);
316 fortranKeywords.add("GOTO", Token.KEYWORD1);
317 fortranKeywords.add("GO TO", Token.KEYWORD1);
318 fortranKeywords.add("IF", Token.KEYWORD1);
319 fortranKeywords.add("INDEX", Token.KEYWORD1);
320 fortranKeywords.add("INQUIRE", Token.KEYWORD1);
321 fortranKeywords.add("OPEN", Token.KEYWORD1);
322 fortranKeywords.add("PRINT", Token.KEYWORD1);
323 fortranKeywords.add("READ", Token.KEYWORD1);
324 fortranKeywords.add("RETURN", Token.KEYWORD1);
325 fortranKeywords.add("THEN", Token.KEYWORD1);
326 fortranKeywords.add("WRITE", Token.KEYWORD1);
327
328 // === Compiler directives ===
329 fortranKeywords.add("BLOCK DATA", Token.KEYWORD2);
330 fortranKeywords.add("COMPILER", Token.KEYWORD2);
331 fortranKeywords.add("END", Token.KEYWORD2);
332 fortranKeywords.add("ENTRY", Token.KEYWORD2);
333 fortranKeywords.add("FUNCTION", Token.KEYWORD2);
334 fortranKeywords.add("INCLUDE", Token.KEYWORD2);
335 fortranKeywords.add("SUBROUTINE", Token.KEYWORD2);
336
337 // === Data types (etc.) ===
338 fortranKeywords.add("CHARACTER", Token.KEYWORD3);
339 fortranKeywords.add("DATA", Token.KEYWORD3);
340 fortranKeywords.add("DEFINE", Token.KEYWORD3);
341 fortranKeywords.add("EQUIVALENCE", Token.KEYWORD3);
342 fortranKeywords.add("IMPLICIT", Token.KEYWORD3);
343 fortranKeywords.add("INTEGER", Token.KEYWORD3);
344 fortranKeywords.add("LOGICAL", Token.KEYWORD3);
345 fortranKeywords.add("PARAMETER", Token.KEYWORD3);
346 fortranKeywords.add("REAL", Token.KEYWORD3);
347
348 // === Operators ===
349 fortranKeywords.add(".AND.", Token.OPERATOR);
350 fortranKeywords.add(".EQ.", Token.OPERATOR);
351 fortranKeywords.add(".NE.", Token.OPERATOR);
352 fortranKeywords.add(".NOT.", Token.OPERATOR);
353 fortranKeywords.add(".OR.", Token.OPERATOR);
354 fortranKeywords.add("+", Token.OPERATOR);
355 fortranKeywords.add("-", Token.OPERATOR);
356 fortranKeywords.add("*", Token.OPERATOR);
357 fortranKeywords.add("**", Token.OPERATOR);
358 fortranKeywords.add("/", Token.OPERATOR);
359
360 // === Literals ===
361 fortranKeywords.add(".FALSE.", Token.LITERAL2);
362 fortranKeywords.add(".TRUE.", Token.LITERAL2);
363
364 }
365 return fortranKeywords;
366 }
367 }
368
369 // End of FortranTokenMarker.java