summaryrefslogtreecommitdiff
path: root/numpy/f2py/lib/parser/readfortran.py
diff options
context:
space:
mode:
authorPearu Peterson <pearu.peterson@gmail.com>2006-10-01 11:49:23 +0000
committerPearu Peterson <pearu.peterson@gmail.com>2006-10-01 11:49:23 +0000
commitc3c53e6805beb164581f041a69c9f51c2740d344 (patch)
tree70d942460a7624996b6a7ee0dd5e25c014b9772f /numpy/f2py/lib/parser/readfortran.py
parent6c52e6fc05f89f13871b79074ea8891c11092d35 (diff)
downloadnumpy-c3c53e6805beb164581f041a69c9f51c2740d344.tar.gz
F2PY G3: Moved Fortran parser related code to subpackage parser.
Diffstat (limited to 'numpy/f2py/lib/parser/readfortran.py')
-rw-r--r--numpy/f2py/lib/parser/readfortran.py829
1 files changed, 829 insertions, 0 deletions
diff --git a/numpy/f2py/lib/parser/readfortran.py b/numpy/f2py/lib/parser/readfortran.py
new file mode 100644
index 000000000..11dd31fda
--- /dev/null
+++ b/numpy/f2py/lib/parser/readfortran.py
@@ -0,0 +1,829 @@
+#!/usr/bin/env python
+"""
+Defines FortranReader classes for reading Fortran codes from
+files and strings. FortranReader handles comments and line continuations
+of both fix and free format Fortran codes.
+
+-----
+Permission to use, modify, and distribute this software is given under the
+terms of the NumPy License. See http://scipy.org.
+
+NO WARRANTY IS EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+Author: Pearu Peterson <pearu@cens.ioc.ee>
+Created: May 2006
+-----
+"""
+
+__all__ = ['FortranFileReader',
+ 'FortranStringReader',
+ 'FortranReaderError',
+ 'Line', 'SyntaxErrorLine',
+ 'Comment',
+ 'MultiLine','SyntaxErrorMultiLine',
+ ]
+
+import re
+import os
+import sys
+import tempfile
+import traceback
+from cStringIO import StringIO
+from numpy.distutils.misc_util import yellow_text, red_text, blue_text
+
+from sourceinfo import get_source_info
+from splitline import String, string_replace_map, splitquote
+
+_spacedigits=' 0123456789'
+_cf2py_re = re.compile(r'(?P<indent>\s*)!f2py(?P<rest>.*)',re.I)
+_is_fix_cont = lambda line: line and len(line)>5 and line[5]!=' ' and line[:5]==5*' '
+_is_f90_cont = lambda line: line and '&' in line and line.rstrip()[-1]=='&'
+_f90label_re = re.compile(r'\s*(?P<label>(\w+\s*:|\d+))\s*(\b|(?=&)|\Z)',re.I)
+_is_include_line = re.compile(r'\s*include\s*("[^"]+"|\'[^\']+\')\s*\Z',re.I).match
+_is_fix_comment = lambda line: line and line[0] in '*cC!'
+_hollerith_start_search = re.compile(r'(?P<pre>\A|,\s*)(?P<num>\d+)h',re.I).search
+_is_call_stmt = re.compile(r'call\b', re.I).match
+
+class FortranReaderError: # TODO: may be derive it from Exception
+ def __init__(self, message):
+ self.message = message
+ print >> sys.stderr,message
+ sys.stderr.flush()
+
+class Line:
+ """ Holds a Fortran source line.
+ """
+
+ f2py_strmap_findall = re.compile(r'(_F2PY_STRING_CONSTANT_\d+_|F2PY_EXPR_TUPLE_\d+)').findall
+
+ def __init__(self, line, linenospan, label, reader):
+ self.line = line.strip()
+ self.span = linenospan
+ self.label = label
+ self.reader = reader
+ self.strline = None
+ self.is_f2py_directive = linenospan[0] in reader.f2py_comment_lines
+
+ def has_map(self):
+ return not not (hasattr(self,'strlinemap') and self.strlinemap)
+
+ def apply_map(self, line):
+ if not hasattr(self,'strlinemap') or not self.strlinemap:
+ return line
+ findall = self.f2py_strmap_findall
+ str_map = self.strlinemap
+ keys = findall(line)
+ for k in keys:
+ line = line.replace(k, str_map[k])
+ return line
+
+ def copy(self, line = None, apply_map = False):
+ if line is None:
+ line = self.line
+ if apply_map:
+ line = self.apply_map(line)
+ return Line(line, self.span, self.label, self.reader)
+
+ def clone(self, line):
+ self.line = self.apply_map(line)
+ self.strline = None
+ return
+
+ def __repr__(self):
+ return self.__class__.__name__+'(%r,%s,%r)' \
+ % (self.line, self.span, self.label)
+
+ def isempty(self, ignore_comments=False):
+ return not (self.line.strip() or self.label)
+
+ def get_line(self):
+ if self.strline is not None:
+ return self.strline
+ line = self.line
+ if self.reader.isfix77:
+ # Handle Hollerith constants by replacing them
+ # with char-literal-constants.
+ # H constants may appear only in DATA statements and
+ # in the argument list of CALL statement.
+ # Holleriht constants were removed from the Fortran 77 standard.
+ # The following handling is not perfect but works for simple
+ # usage cases.
+ # todo: Handle hollerith constants in DATA statement
+ if _is_call_stmt(line):
+ l2 = self.line[4:].lstrip()
+ i = l2.find('(')
+ if i != -1 and l2[-1]==')':
+ substrings = ['call '+l2[:i+1]]
+ start_search = _hollerith_start_search
+ l2 = l2[i+1:-1].strip()
+ m = start_search(l2)
+ while m:
+ substrings.append(l2[:m.start()])
+ substrings.append(m.group('pre'))
+ num = int(m.group('num'))
+ substrings.append("'"+l2[m.end():m.end()+num]+"'")
+ l2 = l2[m.end()+num:]
+ m = start_search(l2)
+ substrings.append(l2)
+ substrings.append(')')
+ line = ''.join(substrings)
+
+ line, str_map = string_replace_map(line, lower=not self.reader.ispyf)
+ self.strline = line
+ self.strlinemap = str_map
+ return line
+
+class SyntaxErrorLine(Line, FortranReaderError):
+ def __init__(self, line, linenospan, label, reader, message):
+ Line.__init__(self, line, linenospan, label, reader)
+ FortranReaderError.__init__(self, message)
+
+class Comment:
+ """ Holds Fortran comment.
+ """
+ def __init__(self, comment, linenospan, reader):
+ self.comment = comment
+ self.span = linenospan
+ self.reader = reader
+ def __repr__(self):
+ return self.__class__.__name__+'(%r,%s)' \
+ % (self.comment, self.span)
+ def isempty(self, ignore_comments=False):
+ return ignore_comments or len(self.comment)<2
+
+class MultiLine:
+ """ Holds (prefix, line list, suffix) representing multiline
+ syntax in .pyf files:
+ prefix+'''+lines+'''+suffix.
+ """
+ def __init__(self, prefix, block, suffix, linenospan, reader):
+ self.prefix = prefix
+ self.block = block
+ self.suffix = suffix
+ self.span = linenospan
+ self.reader = reader
+ def __repr__(self):
+ return self.__class__.__name__+'(%r,%r,%r,%s)' \
+ % (self.prefix,self.block,self.suffix,
+ self.span)
+ def isempty(self, ignore_comments=False):
+ return not (self.prefix or self.block or self.suffix)
+
+class SyntaxErrorMultiLine(MultiLine, FortranReaderError):
+ def __init__(self, prefix, block, suffix, linenospan, reader, message):
+ MultiLine.__init__(self, prefix, block, suffix, linenospan, reader)
+ FortranReaderError.__init__(self, message)
+
+
+class FortranReaderBase:
+
+ def __init__(self, source, isfree, isstrict):
+ """
+ source - file-like object with .next() method
+ used to retrive a line.
+ source may contain
+ - Fortran 77 code
+ - fixed format Fortran 90 code
+ - free format Fortran 90 code
+ - .pyf signatures - extended free format Fortran 90 syntax
+ """
+
+ self.linecount = 0
+ self.source = source
+ self.isclosed = False
+
+ self.filo_line = []
+ self.fifo_item = []
+ self.source_lines = []
+
+ self.f2py_comment_lines = [] # line numbers that contain f2py directives
+
+ self.reader = None
+ self.include_dirs = ['.']
+
+ self.set_mode(isfree, isstrict)
+ return
+
+ def set_mode(self, isfree, isstrict):
+ self.isfree90 = isfree and not isstrict
+ self.isfix90 = not isfree and not isstrict
+ self.isfix77 = not isfree and isstrict
+ self.ispyf = isfree and isstrict
+ self.isfree = isfree
+ self.isfix = not isfree
+ self.isstrict = isstrict
+
+ if self.isfree90: mode = 'free90'
+ elif self.isfix90: mode = 'fix90'
+ elif self.isfix77: mode = 'fix77'
+ else: mode = 'pyf'
+ self.mode = mode
+ self.name = '%s mode=%s' % (self.source, mode)
+ return
+
+ def close_source(self):
+ # called when self.source.next() raises StopIteration.
+ pass
+
+ # For handling raw source lines:
+
+ def put_single_line(self, line):
+ self.filo_line.append(line)
+ self.linecount -= 1
+ return
+
+ def get_single_line(self):
+ try:
+ line = self.filo_line.pop()
+ self.linecount += 1
+ return line
+ except IndexError:
+ pass
+ if self.isclosed:
+ return None
+ try:
+ line = self.source.next()
+ except StopIteration:
+ self.isclosed = True
+ self.close_source()
+ return None
+ self.linecount += 1
+ # expand tabs, replace special symbols, get rid of nl characters
+ line = line.expandtabs().replace('\xa0',' ').rstrip()
+ self.source_lines.append(line)
+ if not line:
+ return self.get_single_line()
+ return line
+
+ def get_next_line(self):
+ line = self.get_single_line()
+ if line is None: return
+ self.put_single_line(line)
+ return line
+
+ # Iterator methods:
+
+ def __iter__(self):
+ return self
+
+ def next(self, ignore_comments = False):
+
+ try:
+ if self.reader is not None:
+ try:
+ return self.reader.next()
+ except StopIteration:
+ self.reader = None
+ item = self._next(ignore_comments)
+ if isinstance(item, Line) and _is_include_line(item.line):
+ reader = item.reader
+ filename = item.line.strip()[7:].lstrip()[1:-1]
+ include_dirs = self.include_dirs[:]
+ path = filename
+ for incl_dir in include_dirs:
+ path = os.path.join(incl_dir, filename)
+ if os.path.exists(path):
+ break
+ if not os.path.isfile(path):
+ dirs = os.pathsep.join(include_dirs)
+ message = reader.format_message(\
+ 'WARNING',
+ 'include file %r not found in %r,'\
+ ' ignoring.' % (filename, dirs),
+ item.span[0], item.span[1])
+ reader.show_message(message, sys.stdout)
+ return self.next(ignore_comments = ignore_comments)
+ message = reader.format_message('INFORMATION',
+ 'found file %r' % (path),
+ item.span[0], item.span[1])
+ reader.show_message(message, sys.stdout)
+ self.reader = FortranFileReader(path, include_dirs = include_dirs)
+ return self.reader.next(ignore_comments = ignore_comments)
+ return item
+ except StopIteration:
+ raise
+ except:
+ message = self.format_message('FATAL ERROR',
+ 'while processing line',
+ self.linecount, self.linecount)
+ self.show_message(message, sys.stdout)
+ traceback.print_exc(file=sys.stdout)
+ self.show_message(red_text('STOPPED READING'), sys.stdout)
+ raise StopIteration
+
+ def _next(self, ignore_comments = False):
+ fifo_item_pop = self.fifo_item.pop
+ while 1:
+ try:
+ item = fifo_item_pop(0)
+ except IndexError:
+ item = self.get_source_item()
+ if item is None:
+ raise StopIteration
+ if not item.isempty(ignore_comments):
+ break
+ # else ignore empty lines and comments
+ if not isinstance(item, Comment):
+ if not self.ispyf and isinstance(item, Line) \
+ and not item.is_f2py_directive \
+ and ';' in item.get_line():
+ # ;-separator not recognized in pyf-mode
+ items = []
+ for line in item.get_line().split(';'):
+ line = line.strip()
+ items.append(item.copy(line, apply_map=True))
+ items.reverse()
+ for newitem in items:
+ self.fifo_item.insert(0, newitem)
+ return fifo_item_pop(0)
+ return item
+ # collect subsequent comments to one comment instance
+ comments = []
+ start = item.span[0]
+ while isinstance(item, Comment):
+ comments.append(item.comment)
+ end = item.span[1]
+ while 1:
+ try:
+ item = fifo_item_pop(0)
+ except IndexError:
+ item = self.get_source_item()
+ if item is None or not item.isempty(ignore_comments):
+ break
+ if item is None:
+ break # hold raising StopIteration for the next call.
+ if item is not None:
+ self.fifo_item.insert(0,item)
+ return self.comment_item('\n'.join(comments), start, end)
+
+ # Interface to returned items:
+
+ def line_item(self, line, startlineno, endlineno, label, errmessage=None):
+ if errmessage is None:
+ return Line(line, (startlineno, endlineno), label, self)
+ return SyntaxErrorLine(line, (startlineno, endlineno),
+ label, self, errmessage)
+
+ def multiline_item(self, prefix, lines, suffix,
+ startlineno, endlineno, errmessage=None):
+ if errmessage is None:
+ return MultiLine(prefix, lines, suffix, (startlineno, endlineno), self)
+ return SyntaxErrorMultiLine(prefix, lines, suffix,
+ (startlineno, endlineno), self, errmessage)
+
+ def comment_item(self, comment, startlineno, endlineno):
+ return Comment(comment, (startlineno, endlineno), self)
+
+ # For handling messages:
+
+ def show_message(self, message, stream = sys.stdout):
+ stream.write(message+'\n')
+ stream.flush()
+
+ def format_message(self, kind, message, startlineno, endlineno,
+ startcolno=0, endcolno=-1):
+ back_index = {'warning':2,'error':3,'info':0}.get(kind.lower(),3)
+ r = ['%s while processing %r (mode=%r)..' % (kind, self.id, self.mode)]
+ for i in range(max(1,startlineno-back_index),startlineno):
+ r.append('%5d:%s' % (i,self.source_lines[i-1]))
+ for i in range(startlineno,min(endlineno+back_index,len(self.source_lines))+1):
+ if i==0 and not self.source_lines:
+ break
+ linenostr = '%5d:' % (i)
+ if i==endlineno:
+ sourceline = self.source_lines[i-1]
+ l0 = linenostr+sourceline[:startcolno]
+ if endcolno==-1:
+ l1 = sourceline[startcolno:]
+ l2 = ''
+ else:
+ l1 = sourceline[startcolno:endcolno]
+ l2 = sourceline[endcolno:]
+ r.append('%s%s%s <== %s' % (l0,yellow_text(l1),l2,red_text(message)))
+ else:
+ r.append(linenostr+ self.source_lines[i-1])
+ return '\n'.join(r)
+
+ def format_error_message(self, message, startlineno, endlineno,
+ startcolno=0, endcolno=-1):
+ return self.format_message('ERROR',message, startlineno,
+ endlineno, startcolno, endcolno)
+
+ def format_warning_message(self, message, startlineno, endlineno,
+ startcolno=0, endcolno=-1):
+ return self.format_message('WARNING',message, startlineno,
+ endlineno, startcolno, endcolno)
+
+ # Auxiliary methods for processing raw source lines:
+
+ def handle_cf2py_start(self, line):
+ """
+ f2py directives can be used only in Fortran codes.
+ They are ignored when used inside .pyf files.
+ """
+ if not line or self.ispyf: return line
+ if self.isfix:
+ if line[0] in '*cC!#':
+ if line[1:5].lower() == 'f2py':
+ line = 5*' ' + line[5:]
+ self.f2py_comment_lines.append(self.linecount)
+ if self.isfix77:
+ return line
+ m = _cf2py_re.match(line)
+ if m:
+ newline = m.group('indent')+5*' '+m.group('rest')
+ self.f2py_comment_lines.append(self.linecount)
+ assert len(newline)==len(line),`newlinel,line`
+ return newline
+ return line
+
+ def handle_inline_comment(self, line, lineno, quotechar=None):
+ if quotechar is None and '!' not in line and \
+ '"' not in line and "'" not in line:
+ return line, quotechar
+ i = line.find('!')
+ put_item = self.fifo_item.append
+ if quotechar is None and i!=-1:
+ # first try a quick method
+ newline = line[:i]
+ if '"' not in newline and '\'' not in newline:
+ if self.isfix77 or not line[i:].startswith('!f2py'):
+ put_item(self.comment_item(line[i:], lineno, lineno))
+ return newline, quotechar
+ # handle cases where comment char may be a part of a character content
+ #splitter = LineSplitter(line, quotechar)
+ #items = [item for item in splitter]
+ #newquotechar = splitter.quotechar
+ items, newquotechar = splitquote(line, quotechar)
+
+ noncomment_items = []
+ noncomment_items_append = noncomment_items.append
+ n = len(items)
+ commentline = None
+ for k in range(n):
+ item = items[k]
+ if isinstance(item, String) or '!' not in item:
+ noncomment_items_append(item)
+ continue
+ j = item.find('!')
+ noncomment_items_append(item[:j])
+ items[k] = item[j:]
+ commentline = ''.join(items[k:])
+ break
+ if commentline is not None:
+ if commentline.startswith('!f2py'):
+ # go to next iteration:
+ newline = ''.join(noncomment_items) + commentline[5:]
+ self.f2py_comment_lines.append(lineno)
+ return self.handle_inline_comment(newline, lineno, quotechar)
+ put_item(self.comment_item(commentline, lineno, lineno))
+ return ''.join(noncomment_items), newquotechar
+
+ def handle_multilines(self, line, startlineno, mlstr):
+ i = line.find(mlstr)
+ if i != -1:
+ prefix = line[:i]
+ # skip fake multiline starts
+ p,k = prefix,0
+ while p.endswith('\\'):
+ p,k = p[:-1],k+1
+ if k % 2: return
+ if i != -1 and '!' not in prefix:
+ # Note character constans like 'abc"""123',
+ # so multiline prefix should better not contain `'' or `"' not `!'.
+ for quote in '"\'':
+ if prefix.count(quote) % 2:
+ message = self.format_warning_message(\
+ 'multiline prefix contains odd number of %r characters' \
+ % (quote), startlineno, startlineno,
+ 0, len(prefix))
+ self.show_message(message, sys.stderr)
+
+ suffix = None
+ multilines = []
+ line = line[i+3:]
+ while line is not None:
+ j = line.find(mlstr)
+ if j != -1 and '!' not in line[:j]:
+ multilines.append(line[:j])
+ suffix = line[j+3:]
+ break
+ multilines.append(line)
+ line = self.get_single_line()
+ if line is None:
+ message = self.format_error_message(\
+ 'multiline block never ends', startlineno,
+ startlineno, i)
+ return self.multiline_item(\
+ prefix,multilines,suffix,\
+ startlineno, self.linecount, message)
+ suffix,qc = self.handle_inline_comment(suffix, self.linecount)
+ # no line continuation allowed in multiline suffix
+ if qc is not None:
+ message = self.format_message(\
+ 'ASSERTION FAILURE(pyf)',
+ 'following character continuation: %r, expected None.' % (qc),
+ startlineno, self.linecount)
+ self.show_message(message, sys.stderr)
+ # XXX: should we do line.replace('\\'+mlstr[0],mlstr[0])
+ # for line in multilines?
+ return self.multiline_item(prefix,multilines,suffix,
+ startlineno, self.linecount)
+
+ # The main method of interpreting raw source lines within
+ # the following contexts: f77, fixed f90, free f90, pyf.
+
+ def get_source_item(self):
+ """
+ a source item is ..
+ - a fortran line
+ - a list of continued fortran lines
+ - a multiline - lines inside triple-qoutes, only when in ispyf mode
+ """
+ get_single_line = self.get_single_line
+ line = get_single_line()
+ if line is None: return
+ startlineno = self.linecount
+ line = self.handle_cf2py_start(line)
+ is_f2py_directive = startlineno in self.f2py_comment_lines
+
+ label = None
+ if self.ispyf:
+ # handle multilines
+ for mlstr in ['"""',"'''"]:
+ r = self.handle_multilines(line, startlineno, mlstr)
+ if r: return r
+
+ if self.isfix:
+ label = line[:5].strip().lower()
+ if label.endswith(':'): label = label[:-1].strip()
+ if not line.strip():
+ # empty line
+ return self.line_item(line[6:],startlineno,self.linecount,label)
+ if _is_fix_comment(line):
+ return self.comment_item(line, startlineno, startlineno)
+ for i in range(5):
+ if line[i] not in _spacedigits:
+ message = 'non-space/digit char %r found in column %i'\
+ ' of fixed Fortran code' % (line[i],i+1)
+ if self.isfix90:
+ message = message + ', switching to free format mode'
+ message = self.format_warning_message(\
+ message,startlineno, self.linecount)
+ self.show_message(message, sys.stderr)
+ self.set_mode(True, False)
+ else:
+ return self.line_item(line[6:], startlineno, self.linecount,
+ label, self.format_error_message(\
+ message, startlineno, self.linecount))
+
+ if self.isfix77 and not is_f2py_directive:
+ lines = [line[6:72]]
+ while _is_fix_cont(self.get_next_line()):
+ # handle fix format line continuations for F77 code
+ line = get_single_line()
+ lines.append(line[6:72])
+ return self.line_item(''.join(lines),startlineno,self.linecount,label)
+
+ handle_inline_comment = self.handle_inline_comment
+
+ if self.isfix90 and not is_f2py_directive:
+ # handle inline comment
+ newline,qc = handle_inline_comment(line[6:], startlineno)
+ lines = [newline]
+ next_line = self.get_next_line()
+ while _is_fix_cont(next_line) or _is_fix_comment(next_line):
+ # handle fix format line continuations for F90 code.
+ # mixing fix format and f90 line continuations is not allowed
+ # nor detected, just eject warnings.
+ line2 = get_single_line()
+ if _is_fix_comment(line2):
+ # handle fix format comments inside line continuations
+ citem = self.comment_item(line2,self.linecount,self.linecount)
+ self.fifo_item.append(citem)
+ else:
+ newline, qc = self.handle_inline_comment(line2[6:],
+ self.linecount, qc)
+ lines.append(newline)
+ next_line = self.get_next_line()
+ # no character continuation should follows now
+ if qc is not None:
+ message = self.format_message(\
+ 'ASSERTION FAILURE(fix90)',
+ 'following character continuation: %r, expected None.'\
+ % (qc), startlineno, self.linecount)
+ self.show_message(message, sys.stderr)
+ if len(lines)>1:
+ for i in range(len(lines)):
+ l = lines[i]
+ if l.rstrip().endswith('&'):
+ message = self.format_warning_message(\
+ 'f90 line continuation character `&\' detected'\
+ ' in fix format code',
+ startlineno + i, startlineno + i, l.rfind('&')+5)
+ self.show_message(message, sys.stderr)
+ return self.line_item(''.join(lines),startlineno,
+ self.linecount,label)
+ start_index = 0
+ if self.isfix90:
+ start_index = 6
+
+ lines = []
+ lines_append = lines.append
+ put_item = self.fifo_item.append
+ qc = None
+ while line is not None:
+ if start_index: # fix format code
+ line,qc = handle_inline_comment(line[start_index:],
+ self.linecount,qc)
+ is_f2py_directive = self.linecount in self.f2py_comment_lines
+ else:
+ line_lstrip = line.lstrip()
+ if lines:
+ if line_lstrip.startswith('!'):
+ # check for comment line within line continuation
+ put_item(self.comment_item(line_lstrip,
+ self.linecount, self.linecount))
+ line = get_single_line()
+ continue
+ else:
+ # first line, check for a f90 label
+ m = _f90label_re.match(line)
+ if m:
+ assert not label,`label,m.group('label')`
+ label = m.group('label').strip()
+ if label.endswith(':'): label = label[:-1].strip()
+ if not self.ispyf: label = label.lower()
+ line = line[m.end():]
+ line,qc = handle_inline_comment(line, self.linecount, qc)
+ is_f2py_directive = self.linecount in self.f2py_comment_lines
+
+ i = line.rfind('&')
+ if i!=-1:
+ line_i1_rstrip = line[i+1:].rstrip()
+ if not lines:
+ # first line
+ if i == -1 or line_i1_rstrip:
+ lines_append(line)
+ break
+ lines_append(line[:i])
+ line = get_single_line()
+ continue
+ if i == -1 or line_i1_rstrip:
+ # no line continuation follows
+ i = len(line)
+ k = -1
+ if i != -1:
+ # handle the beggining of continued line
+ k = line[:i].find('&')
+ if k != 1 and line[:k].lstrip():
+ k = -1
+ lines_append(line[k+1:i])
+ if i==len(line):
+ break
+ line = get_single_line()
+
+ if qc is not None:
+ message = self.format_message('ASSERTION FAILURE(free)',
+ 'following character continuation: %r, expected None.' % (qc),
+ startlineno, self.linecount)
+ self.show_message(message, sys.stderr)
+ return self.line_item(''.join(lines),startlineno,self.linecount,label)
+
+ ## FortranReaderBase
+
+# Fortran file and string readers:
+
+class FortranFileReader(FortranReaderBase):
+
+ def __init__(self, filename,
+ include_dirs = None):
+ isfree, isstrict = get_source_info(filename)
+ self.id = filename
+ self.file = open(filename,'r')
+ FortranReaderBase.__init__(self, self.file, isfree, isstrict)
+ if include_dirs is None:
+ self.include_dirs.insert(0, os.path.dirname(filename))
+ else:
+ self.include_dirs = include_dirs[:]
+ return
+
+ def close_source(self):
+ self.file.close()
+
+class FortranStringReader(FortranReaderBase):
+
+ def __init__(self, string, isfree, isstrict, include_dirs = None):
+ self.id = 'string-'+str(id(string))
+ source = StringIO(string)
+ FortranReaderBase.__init__(self, source, isfree, isstrict)
+ if include_dirs is not None:
+ self.include_dirs = include_dirs[:]
+ return
+
+# Testing:
+
+def test_f77():
+ string_f77 = """
+c12346 comment
+ subroutine foo
+ call foo
+ 'bar
+a 'g
+ abc=2
+cf2py call me ! hey
+ call you ! hi
+ end
+ '"""
+ reader = FortranStringReader(string_f77,False,True)
+ for item in reader:
+ print item
+
+ filename = tempfile.mktemp()+'.f'
+ f = open(filename,'w')
+ f.write(string_f77)
+ f.close()
+
+ reader = FortranFileReader(filename)
+ for item in reader:
+ print item
+
+def test_pyf():
+ string_pyf = """\
+python module foo
+ interface
+ beginml '''1st line
+ 2nd line
+ end line'''endml='tere!fake comment'!should be a comment
+ a = 2
+ 'charc\"onstant' ''' single line mline '''a='hi!fake comment'!should be a comment
+ a=\\\\\\\\\\'''not a multiline'''
+ !blah='''never ending multiline
+ b=3! hey, fake line continuation:&
+ c=4& !line cont
+ &45
+ thisis_label_2 : c = 3
+ xxif_isotropic_2 : if ( string_upper_compare ( o%opt_aniso, 'ISOTROPIC' ) ) then
+ g=3
+ endif
+ end interface
+ if ( pc_get_lun() .ne. 6) &
+
+ write ( pc_get_lun(), '( &
+ & /, a, /, " p=", i4, " stopping c_flag=", a, &
+ & /, " print unit=", i8)') &
+ trim(title), pcpsx_i_pel(), trim(c_flag), pc_get_lun()
+end python module foo
+! end of file
+"""
+ reader = FortranStringReader(string_pyf,True, True)
+ for item in reader:
+ print item
+
+def test_fix90():
+ string_fix90 = """\
+ subroutine foo
+cComment
+ 1234 a = 3 !inline comment
+ b = 3
+!
+ !4!line cont. with comment symbol
+ &5
+ a = 3!f2py.14 ! pi!
+! KDMO
+ write (obj%print_lun, *) ' KDMO : '
+ write (obj%print_lun, *) ' COORD = ',coord, ' BIN_WID = ', &
+ obj%bin_wid,' VEL_DMO = ', obj%vel_dmo
+ end subroutine foo
+ subroutine
+
+ & foo
+ end
+"""
+ reader = FortranStringReader(string_fix90,False, False)
+ for item in reader:
+ print item
+
+def simple_main():
+ for filename in sys.argv[1:]:
+ print 'Processing',filename
+ reader = FortranFileReader(filename)
+ for item in reader:
+ print >> sys.stdout, item
+ sys.stdout.flush()
+ pass
+
+def profile_main():
+ import hotshot, hotshot.stats
+ prof = hotshot.Profile("readfortran.prof")
+ prof.runcall(simple_main)
+ prof.close()
+ stats = hotshot.stats.load("readfortran.prof")
+ stats.strip_dirs()
+ stats.sort_stats('time', 'calls')
+ stats.print_stats(30)
+
+if __name__ == "__main__":
+ #test_pyf()
+ #test_fix90()
+ #profile_main()
+ simple_main()