001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.cnd.api.utils;
043:
044: import java.io.FileInputStream;
045: import java.io.FileNotFoundException;
046: import java.io.IOException;
047: import java.io.PushbackInputStream;
048: import java.util.StringTokenizer;
049:
050: /**
051: * Read a Fortran file. Return complete Fortran statements, taking into consideration
052: * things like the source form, line length, continuation lines, and comments. This
053: * class works in conjunction with FortranParse to parse fortran files for mod and use
054: * statements.
055: */
056: public class FortranReader {
057:
058: /** The input stream to the source file */
059: private PushbackInputStream in;
060:
061: /** The statement buffer */
062: private StringBuffer statement = new StringBuffer(256);
063:
064: /** Are we doing free format? */
065: private boolean freeFormat;
066:
067: /** Are we doing fpp preprocessing? */
068: private boolean preprocess;
069:
070: /** Use 132 character lines if set */
071: private boolean longLines;
072:
073: /** Miscelaneous StringBuffer (class scope to reuse) */
074: private StringBuffer sbuf = new StringBuffer();
075:
076: /** Copy of the current character read */
077: private byte curc = 0;
078:
079: /** Previous curc */
080: private byte lastc;
081:
082: /** Save the quote start character for multi-line quotes */
083: private byte qchar;
084:
085: /** Previous lastc (used to restore lastc after ungetc()) */
086: private byte prevlastc;
087:
088: /** Column number of last character read */
089: private int col;
090:
091: /** Save the last column number so it can be restored during ungetc() calls */
092: int lastcol = 0;
093:
094: /** The marker we use for end of file */
095: private final static byte EOF = (byte) 0xff;
096:
097: /** Keep track of the line number */
098: private int lineno;
099:
100: /** line number at the start of a statement */
101: private int lnum;
102:
103: /** Keep track of what state we are in */
104: private State state;
105:
106: private final State StartOfLine = new State("StartOfLine"); // NOI18N
107: private final State EndOfLine = new State("EndOfLine"); // NOI18N
108: private final State StartOfStatement = new State("StartOfStatement"); // NOI18N
109: private final State EndOfStatement = new State("EndOfStatement"); // NOI18N
110: private final State InStatement = new State("InStatement"); // NOI18N
111: private final State InComment = new State("InComment"); // NOI18N
112: private final State InQuote = new State("InQuote"); // NOI18N
113: private final State GotEOF = new State("GotEOF"); // NOI18N
114:
115: private boolean verbose = false;
116:
117: /**
118: * Create for reading, starting with current source line format
119: *
120: * @param file The name of the file to read
121: * @param options The Fortran options in effect
122: * @return A single complete Fortran statement
123: */
124: public FortranReader(String file, String options, boolean verbose)
125: throws FileNotFoundException {
126:
127: this .verbose = verbose;
128: try {
129: in = new PushbackInputStream(new FileInputStream(file), 1);
130: } catch (IllegalArgumentException ex) {
131: }
132:
133: lineno = 1;
134: col = 0;
135: state = StartOfLine;
136:
137: setModes(file, options);
138: }
139:
140: public FortranReader(String file, String options)
141: throws FileNotFoundException {
142: this (file, options, false);
143: }
144:
145: /**
146: * Read and return a complete Fortran statement, taking consideration of Source
147: * Line Formats. The statement is NOT newline terminated.
148: *
149: * @return A single complete fortran statement
150: */
151: public String getStatement() throws UnexpectedEOFException {
152: String stmnt;
153: byte c;
154:
155: statement.delete(0, statement.length());
156: while (true) {
157: stmnt = null;
158: if (state == StartOfLine) {
159: state = readStartOfLine();
160: lnum = lineno;
161: if (state == StartOfStatement && statement.length() > 0) {
162: stmnt = statement.toString().trim();
163: if (stmnt.length() > 0) {
164: break;
165: }
166: }
167: } else if (state == EndOfLine) {
168: if ((c = getc()) == '\n') {
169: state = StartOfLine;
170: }
171: } else if (state == StartOfStatement
172: || state == InStatement) {
173: state = readStatement();
174: } else if (state == EndOfStatement) {
175: if ((c = getc()) == ';') {
176: state = StartOfStatement;
177: stmnt = statement.toString().trim();
178: }
179: } else if (state == InComment) {
180: state = readUntilEOL();
181: } else if (state == InQuote) {
182: state = continueQuote();
183: } else if (state == GotEOF) { // Got an EOF
184: if (curc == -1 && lastc == '\n') {
185: stmnt = statement.toString().trim();
186: if (stmnt.length() > 0) {
187: break;
188: } else {
189: return null;
190: }
191: } else {
192: throw new UnexpectedEOFException();
193: }
194: }
195: }
196:
197: return stmnt;
198: }
199:
200: /** Read bytes at the start of line until we determine the new state */
201: private State readStartOfLine() {
202: String buf;
203: byte c = getc();
204:
205: if (c == '!' || (!freeFormat && isComment((char) c))) {
206: buf = getbytes(4);
207: if (buf.equalsIgnoreCase("dir$")) { // NOI18N
208: trimInput();
209: buf = getbytes(5);
210: if (buf.equalsIgnoreCase("fixed")) { // NOI18N
211: freeFormat = false;
212: } else if (buf.length() >= 4
213: && buf.substring(0, 4).equalsIgnoreCase("free")) { // NOI18N
214: freeFormat = true;
215: }
216: }
217: return InComment;
218: } else if (c == '\n') {
219: return state;
220: }
221:
222: if (freeFormat) {
223: ungetc(c);
224: return nextState();
225: } else {
226: buf = getbytes(5);
227: if (buf.length() == 5 && isContinuation(buf.charAt(4))) {
228: return InStatement;
229: } else {
230: return nextState();
231: }
232: }
233: }
234:
235: /**
236: * Read bytes until we can determine a new state. This method is only valid in
237: * the state is StartOfLine. If called in an invalide state it returns the current
238: * state without reading any bytes.
239: */
240: private State nextState() {
241: byte c;
242:
243: if (state == StartOfLine) {
244: while ((c = getc()) != EOF) {
245: if (col > 132
246: || (!freeFormat && !longLines && col > 72)) {
247: return readUntilEOL();
248: } else if (isSpace((char) c)) {
249: continue;
250: } else if (c == '!') {
251: return readUntilEOL();
252: } else if (c == ';') {
253: ungetc(c);
254: return EndOfStatement;
255: } else if (c == '\n') {
256: ungetc(c);
257: return EndOfLine;
258: } else if (c == '\'' || c == '"') {
259: return readQuote(c, true);
260: } else {
261: ungetc(c);
262: return StartOfStatement;
263: }
264: }
265:
266: return GotEOF;
267: } else {
268: return state;
269: }
270: }
271:
272: /** Read until we reach the end of a statement */
273: private State readStatement() {
274: byte c;
275:
276: while ((c = getc()) != EOF) {
277: if (freeFormat
278: && col > 132
279: || (!freeFormat && (col > 72 || (longLines && col > 132)))) {
280: return readUntilEOL();
281: } else if (c == '\n') {
282: ungetc(c);
283: return EndOfLine;
284: } else if (c == '\'' || c == '"') {
285: return readQuote(c, true);
286: } else if (freeFormat && c == '&') {
287: ungetc(c);
288: return EndOfLine;
289: } else if (c == ';') {
290: ungetc(c);
291: return EndOfStatement;
292: } else if (c != ' ') {
293: statement.append((char) c);
294: }
295: }
296:
297: return GotEOF;
298: }
299:
300: /** Read a quote. Be sure to ignore embedded quotes */
301: private State readQuote(byte qchar, boolean printQchar) {
302: byte c;
303:
304: this .qchar = qchar;
305: if (printQchar) {
306: statement.append((char) qchar);
307: }
308: while ((c = getc()) != EOF) {
309: if ((c == qchar && peekc() != qchar && lastc != qchar)) {
310: // The end of the quote was found
311: statement.append((char) c);
312: return InStatement;
313: } else if (freeFormat && c == '&' && peekc() == '\n') {
314: // The quote is being continued
315: return InQuote;
316: } else if (c == '\n') {
317: // The quote is being continued
318: ungetc(c);
319: return InQuote;
320: } else {
321: statement.append((char) c);
322: }
323: }
324:
325: return GotEOF;
326: }
327:
328: /** Read more of the quote */
329: private State continueQuote() {
330: byte c;
331:
332: if (freeFormat) {
333: if (curc == '&' && peekc() == '\n') {
334: getc(); // read the '\n'
335: sbuf.delete(0, sbuf.length());
336: while ((c = getc()) != EOF) {
337: if (c == ' ' || c == '\t') {
338: sbuf.append(c);
339: } else if (c == '&') {
340: return readQuote(qchar, false);
341: } else if (c == '!') {
342: readUntilEOL();
343: } else if (c == '\n') {
344: statement.append(sbuf);
345: sbuf.delete(0, sbuf.length());
346: } else {
347: statement.append(sbuf);
348: ungetc(c);
349: return readQuote(qchar, false);
350: }
351: }
352: return GotEOF;
353: } else if (curc == '\n') {
354: return readQuote(qchar, false);
355: } else {
356: // Error condition
357: readUntilEOL();
358: return EndOfLine;
359: }
360: } else {
361: getc(); // read the '\n'
362: while (true) {
363: sbuf.delete(0, sbuf.length());
364: while ((c = getc()) == ' ' || c == '\t') {
365: sbuf.append(c);
366: }
367: if ((col == 1 && (c == 'C' || c == '*'))
368: || (col < 6 && c == '!')) {
369: readUntilEOL();
370: } else if (c == EOF) {
371: return GotEOF;
372: } else if (c == '\n') {
373: continue;
374: } else if (col == 6 && isContinuation((char) c)) {
375: return readQuote(qchar, false);
376: } else if (col > 6) {
377: statement.append(sbuf);
378: statement.append((char) c);
379: return readQuote(qchar, false);
380: }
381: }
382: }
383:
384: }
385:
386: /**
387: * Read input until the desired state is reached.
388: *
389: * @return Either EndOfLine or GotEOF
390: */
391: private State readUntilEOL() {
392: byte c;
393:
394: while ((c = getc()) != EOF) {
395: if (c == '\n') {
396: ungetc(c);
397: return EndOfLine;
398: }
399: }
400: return GotEOF;
401: }
402:
403: /** Trim spaces and tabs from the input */
404: private void trimInput() {
405: byte c;
406:
407: do {
408: c = getc();
409: } while (c == ' ' || c == '\t');
410: ungetc(c);
411: }
412:
413: private boolean isComment(char c) {
414: return "cC!dD*".indexOf(c) != -1; // NOI18N
415: }
416:
417: private boolean isSpace(char c) {
418: return " \t".indexOf(c) != -1; // NOI18N
419: }
420:
421: private boolean isContinuation(char c) {
422: return "0 ".indexOf(c) == -1; // NOI18N
423: }
424:
425: /**
426: * Get a specific number of bytes.
427: *
428: * @param num The number of bytes to put in the buffer
429: * @return A String with up to num characters
430: */
431: private String getbytes(int num) {
432:
433: if (num < 0) {
434: return null;
435: }
436:
437: sbuf.delete(0, sbuf.length());
438: for (int i = 0; i < num; i++) {
439: byte c = getc();
440:
441: if (c == '\n') {
442: ungetc(c);
443: break;
444: }
445: sbuf.append((char) c);
446: }
447:
448: return sbuf.toString();
449: }
450:
451: /** Get a single character */
452: private byte getc() {
453:
454: if (curc == EOF) {
455: // Don't bother reading any more
456: return EOF;
457: }
458:
459: try {
460: prevlastc = lastc; // lets me restore lastc after ungetc(byte)
461: lastc = curc;
462: curc = (byte) (in.read() & 0xff);
463:
464: if (curc == '\n') {
465: lastcol = col;
466: lineno++;
467: col = 0;
468: } else if (curc == -1) {
469: return EOF;
470: } else {
471: col++;
472: }
473: return curc;
474: } catch (IOException ex) {
475: return EOF;
476: }
477: }
478:
479: /** Peek at the next character without reading it */
480: private byte peekc() {
481: byte nextc = getc();
482: ungetc(nextc);
483: return nextc;
484: }
485:
486: /** Unread the character */
487: private void ungetc(byte b) {
488:
489: if (b != EOF) {
490: if (b == '\n') {
491: lineno--; // otherwise it will get incremented a 2nd time next getc()
492: col = lastcol;
493: } else {
494: col--;
495: }
496: try {
497: in.unread(b);
498: curc = lastc;
499: lastc = prevlastc;
500: prevlastc = 0;
501: } catch (IOException ex) {
502: println("*** Unread too many bytes ***"); // NOI18N
503: }
504: }
505: }
506:
507: /**
508: * Set the initial line format from the names and options. Also check a few
509: * other options that we need to know about.
510: */
511: private void setModes(String file, String options) {
512: String ext;
513: int pos;
514:
515: // First, check the file extension. This controls several options.
516: pos = file.lastIndexOf('.');
517: if (pos >= 0) {
518: ext = file.substring(pos + 1);
519: if (ext.equals("f90") || ext.equals("f95")) { // NOI18N
520: freeFormat = true;
521: } else if (ext.equals("F90") || ext.equals("F95")) { // NOI18N
522: freeFormat = true;
523: preprocess = true;
524: } else if (ext.equals(".f") || ext.equals("for")) { // NOI18N
525: freeFormat = false;
526: } else if (ext.equals("F")) { // NOI18N
527: freeFormat = false;
528: preprocess = true;
529: }
530: } else {
531: //throw new IllegalArgumentException(NbBundle.getMessage(FortranReader.class,
532: //"InvalidFortranFileName", file)); // NOI18N
533: }
534:
535: // Next, check the options string. Some of these options override ones set by
536: // the file extension (which is why we did the extension processing first).
537: if (options != null && options.length() > 0) {
538: StringTokenizer st = new StringTokenizer(options);
539:
540: while (st.hasMoreTokens()) {
541: String opt = st.nextToken();
542:
543: if (opt.equals("-free")) { // NOI18N
544: freeFormat = true;
545: } else if (opt.equals("-fixed")) { // NOI18N
546: freeFormat = false;
547: } else if (opt.equals("-e")) { // NOI18N
548: longLines = true;
549: } else if (opt.equals("-fpp")) { // NOI18N
550: preprocess = true;
551: }
552: }
553: }
554: }
555:
556: /** An simple enumeration class */
557: public class State {
558: private final String name;
559:
560: public State(String name) {
561: this .name = name;
562: }
563:
564: public String toString() {
565: return name;
566: }
567: }
568:
569: // The following code is all debug code. It doesn't need any i18n translation
570: // nor does it get tested.
571: private void println(String msg) {
572:
573: if (verbose) {
574: System.out.println(msg);
575: }
576: }
577:
578: public static void main(String[] args) {
579: FortranReader fr = null;
580: boolean verbose = false;
581: String file = null;
582:
583: for (int i = 0; i < args.length; i++) {
584: if (args[i].equals("-v")) { // NOI18N
585: verbose = true;
586: } else {
587: file = args[i];
588: }
589: }
590:
591: if (file == null) {
592: file = "fwutil.f"; // NOI18N
593: }
594:
595: try {
596: fr = new FortranReader(file, null);
597: fr.verbose = verbose;
598: String line;
599:
600: int i = 0;
601: while ((line = fr.getStatement()) != null) {
602: int lineno = fr.lnum - 1;
603: System.out.println(lineno + ": " + line); // NOI18N
604: }
605: } catch (UnexpectedEOFException unex) {
606: System.err.println("Unexpected EOF exception"); // NOI18N
607: } catch (FileNotFoundException fnfex) {
608: System.err.println("File not found exception"); // NOI18N
609: } catch (Exception ex) {
610: ex.printStackTrace();
611: }
612: }
613: }
|