Example #1
0
class FortranReader(object):
    """
    An iterator which will convert a free-form Fortran source file into
    a format more conducive for analyzing. It does the following:
        - combine line continuations into one
        - remove any normal comments and any comments following an ampersand
          (line continuation)
        - if there are documentation comments preceding a piece of code, buffer
          them and return them after the code, but before any documentation
          following it
        - keep any documentation comments and, if they are at the end of a line
          of actual code, place them on a new line
        - removes blank lines and trailing white-space
        - split lines along semicolons
    """

    # Regexes
    COM_RE = re.compile("^([^\"'!]|(\'[^']*')|(\"[^\"]*\"))*(!.*)$")
    SC_RE = re.compile("^([^;]*);(.*)$")

    def __init__(self,
                 filename,
                 docmark='!',
                 predocmark='',
                 docmark_alt='',
                 predocmark_alt='',
                 fixed=False,
                 length_limit=True,
                 preprocessor=None,
                 macros=[],
                 inc_dirs=[]):
        self.name = filename

        # Check that none of the docmarks are the same
        if docmark == predocmark != '':
            raise Exception('Error: docmark and predocmark are the same.')
        if docmark == docmark_alt != '':
            raise Exception('Error: docmark and docmark_alt are the same.')
        if docmark == predocmark_alt != '':
            raise Exception('Error: docmark and predocmark_alt are the same.')
        if docmark_alt == predocmark != '':
            raise Exception('Error: docmark_alt and predocmark are the same.')
        if docmark_alt == predocmark_alt != '':
            raise Exception(
                'Error: docmark_alt and predocmark_alt are the same.')
        if predocmark == predocmark_alt != '':
            raise Exception(
                'Error: predocmark and predocmark_alt are the same.')

        if preprocessor:
            # Populate the macro definition and include directory path from
            # the input lists.  To define a macro we prepend '-D' and for an
            # include path we prepend '-I'.  It's important that we do not
            # prepend to an empty string as 'cpp ... -D file.F90' doesn't do
            # what is desired; use filter to remove these.
            macros = ['-D' + mac.strip() for mac in filter(None, macros)]
            incdirs = ['-I' + d.strip() for d in filter(None, inc_dirs)]
            preprocessor = preprocessor + macros + incdirs + [filename]
            fpp = subprocess.Popen(preprocessor,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   universal_newlines=True)
            (out, err) = fpp.communicate()

            if len(err) > 0:
                print('Warning: error preprocessing ' + filename)
                print(err)
                self.reader = open(filename, 'r')
            else:
                self.reader = StringIO(str(out))
        else:
            self.reader = open(filename, 'r')

        if fixed:
            self.reader = convertToFree(self.reader, length_limit)

        self.fixed = fixed
        self.length_limit = length_limit
        self.inc_dirs = inc_dirs
        self.docbuffer = []
        self.pending = []
        self.prevdoc = False
        self.reading_alt = 0
        self.docmark = docmark
        self.doc_re = re.compile(
            "^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(
                re.escape(docmark)))
        self.predocmark = predocmark
        if len(self.predocmark) != 0:
            self.predoc_re = re.compile(
                "^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(
                    re.escape(predocmark)))
        else:
            self.predoc_re = None
        self.docmark_alt = docmark_alt
        if len(self.docmark_alt) != 0:
            self.doc_alt_re = re.compile(
                "^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(
                    re.escape(docmark_alt)))
        else:
            self.doc_alt_re = None
        self.predocmark_alt = predocmark_alt
        if len(self.predocmark_alt) != 0:
            self.predoc_alt_re = re.compile(
                "^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(
                    re.escape(predocmark_alt)))
        else:
            self.predoc_alt_re = None

    def __iter__(self):
        return self

    def pass_back(self, line):
        self.pending.insert(0, line)

    #for Python 2:
    def next(self):
        return self.__next__()

    def __next__(self):  # Python 3
        # If there are any lines waiting to be returned, return them
        if len(self.pending) != 0:
            self.include()
            self.prevdoc = False
            return self.pending.pop(0)
        # If there are any documentation lines waiting to be returned, return them.
        # This can happen for inline and preceding docs
        elif len(self.docbuffer) != 0:
            self.prevdoc = True
            return self.docbuffer.pop(0)

        # Loop through the source code until you have a complete line (including
        # all line continuations), or a complete preceding doc block
        done = False
        continued = False
        reading_predoc = False
        reading_predoc_alt = 0
        linebuffer = ""
        count = 0
        while not done:
            count += 1

            if (sys.version_info[0] > 2):
                line = self.reader.__next__()
            else:  #Python 2
                line = self.reader.next()
            if len(line.strip()) > 0 and line.strip()[0] == '#': continue

            # Capture any preceding documenation comments
            if self.predoc_re:
                match = self.predoc_re.match(line)
            else:
                match = False
            if match:
                # Switch to predoc: following comment lines are predoc until the end of the block
                reading_predoc = True
                self.reading_alt = 0
                readeing_predoc_alt = 0
                # Substitute predocmark with docmark
                tmp = match.group(4)
                tmp = tmp[:1] + self.docmark + tmp[1 + len(self.predocmark):]
                self.docbuffer.append(tmp)
                if len(line[0:match.start(4)].strip()) > 0:
                    raise Exception(
                        "Preceding documentation lines can not be inline")

            # Check for alternate preceding documentation
            if self.predoc_alt_re:
                match = self.predoc_alt_re.match(line)
            else:
                match = False
            if match:
                # Switch to doc_alt: following comment lines are documentation until end of the block
                reading_predoc_alt = 1
                self.reading_alt = 0
                reading_predoc = False
                # Substitute predocmark_alt with docmark
                tmp = match.group(4)
                tmp = tmp[:1] + self.docmark + tmp[1 +
                                                   len(self.predocmark_alt):]
                self.docbuffer.append(tmp)
                if len(line[0:match.start(4)].strip()) > 0:
                    raise Exception(
                        "Alternate documentation lines can not be inline")

            # Check for alternate succeeding documentation
            if self.doc_alt_re:
                match = self.doc_alt_re.match(line)
            else:
                match = False
            if match:
                # Switch to doc_alt: following comment lines are documentation until end of the block
                self.reading_alt = 1
                reading_predoc = False
                reading_predoc_alt = 0
                # Substitute predocmark_alt with docmark
                tmp = match.group(4)
                tmp = tmp[:1] + self.docmark + tmp[1 + len(self.docmark_alt):]
                self.docbuffer.append(tmp)
                if len(line[0:match.start(4)].strip()) > 0:
                    raise Exception(
                        "Alternate documentation lines can not be inline")

            # Capture any documentation comments
            match = self.doc_re.match(line)
            if match:
                self.reading_alt = 0
                reading_predoc_alt = 0
                self.docbuffer.append(match.group(4))
                line = line[0:match.start(4)]

            if len(line.strip()) == 0 or line.strip()[0] != '!':
                self.reading_alt = 0

            if len(line.strip()) != 0 and line.strip()[0] != '!':
                reading_predoc_alt = 0

            # Remove any regular comments, unless following an alternative (pre)docmark
            match = self.COM_RE.match(line)
            if match:
                if (reading_predoc_alt > 1 or self.reading_alt > 1) and len(
                        line[0:match.start(4)].strip()) == 0:
                    tmp = match.group(4)
                    tmp = tmp[:1] + self.docmark + tmp[1:]
                    self.docbuffer.append(tmp)
                line = line[0:match.start(4)]
            line = line.strip()

            # If this is a blank line following previous documentation, return
            # a line of empty documentation.
            if len(line) == 0:
                if self.prevdoc and len(self.docbuffer) == 0:
                    #~ self.prevdoc = False
                    self.docbuffer.append("!" + self.docmark)
            else:
                reading_predoc = False
                reading_predoc_alt = 0
                self.reading_alt = 0
                # Check if line is immediate continuation of previous
                if line[0] == '&':
                    if continued:
                        line = line[1:]
                    else:
                        raise Exception(
                            "Can not start a new line in Fortran with \"&\"")
                else:
                    linebuffer = linebuffer.strip() + ' '
                # Check if line will be continued
                if line[-1] == '&':
                    continued = True
                    line = line[0:-1]
                else:
                    continued = False

            if self.reading_alt > 0:
                self.reading_alt += 1

            if reading_predoc_alt > 0:
                reading_predoc_alt += 1

            # Add this line to the buffer then check whether we're done here
            linebuffer += line
            #~ print(((len(self.docbuffer) > 0) or (len(linebuffer) > 0)), not continued, not reading_predoc, (reading_predoc_alt == 0))
            done = (((len(self.docbuffer) > 0) or (len(linebuffer) > 0))
                    and not continued and not reading_predoc
                    and (reading_predoc_alt == 0))

        # Split buffer with semicolons
        #~ print(count,linebuffer,len(linebuffer))
        frags = ford.utils.quote_split(';', linebuffer)
        self.pending.extend([s.strip() for s in frags if len(s) > 0])

        # Return the line
        if len(self.pending) > 0:
            self.include()
            self.prevdoc = False
            return self.pending.pop(0)
        else:
            tmp = self.docbuffer.pop(0)
            if tmp != "!" + self.docmark:
                self.prevdoc = True
            return tmp

    def include(self):
        """
        If the next line is an include statement, inserts the contents
        of the included file into the pending buffer.
        """
        if len(self.pending
               ) == 0 or not self.pending[0].startswith('include '):
            return
        name = self.pending.pop(0)[8:].strip()[1:-1]
        for b in [os.path.dirname(self.name)] + self.inc_dirs:
            pname = os.path.abspath(os.path.expanduser(os.path.join(b, name)))
            if os.path.isfile(pname):
                name = pname
                break
        else:
            raise Exception('Can not find include file "{}".'.format(name))
        self.pending = list(
            FortranReader(name,
                          self.docmark,
                          self.predocmark,
                          self.docmark_alt,
                          self.predocmark_alt,
                          self.fixed,
                          self.length_limit,
                          inc_dirs=self.inc_dirs)) + self.pending
Example #2
0
class S3File(object):

    def __init__(self, url, key=None, secret=None, expiration_days=0, private=False, content_type=None, create=True):
        from boto.s3.connection import S3Connection
        from boto.s3.key import Key

        if sys.version_info < (3, 0):
            self.url = urlparse(url)
            self.buffer = cStringIO.StringIO()
        else:
            self.url = urlparse3(url)
            self.buffer = StringIO()

        self.expiration_days = expiration_days

        self.private = private
        self.closed = False
        self._readreq = True
        self._writereq = False
        self.content_type = content_type or mimetypes.guess_type(self.url.path)[0]

        bucket = self.url.netloc
        if bucket.endswith('.s3.amazonaws.com'):
            bucket = bucket[:-17]

        self.client = S3Connection(key, secret)

        self.name = "s3://" + bucket + self.url.path

        if create:
            self.bucket = self.client.create_bucket(bucket)
        else:
            self.bucket = self.client.get_bucket(bucket, validate=False)

        self.key = Key(self.bucket)
        self.key.key = self.url.path.lstrip("/")
        self.buffer.truncate(0)

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.close()

    def _remote_read(self):
        """ Read S3 contents into internal file buffer.
            Once only
        """
        if self._readreq:
                self.buffer.truncate(0)
                if self.key.exists():
                    self.key.get_contents_to_file(self.buffer)
                self.buffer.seek(0)
                self._readreq = False

    def _remote_write(self):
        """ Write file contents to S3 from internal buffer.
        """
        if self._writereq:
            self.truncate(self.tell())

            headers = {
                "x-amz-acl":  "private" if self.private else "public-read"
            }

            if self.content_type:
                headers["Content-Type"] = self.content_type

            if self.expiration_days:
                now = datetime.datetime.utcnow()
                then = now + datetime.timedelta(self.expiration_days)
                headers["Expires"] = then.strftime("%a, %d %b %Y %H:%M:%S GMT")
                headers["Cache-Control"] = 'max-age=%d' % (self.expiration_days * 24 * 3600,)

            self.key.set_contents_from_file(self.buffer, headers=headers, rewind=True)

    def close(self):
        """ Close the file and write contents to S3.
        """
        self._remote_write()
        self.buffer.close()
        self.closed = True

    # pass-through methods

    def flush(self):
        self._remote_write()

    def fileno(self):
        return 3

    def next(self):
        self._remote_read()
        return self.buffer.next()

    def read(self, size=-1):
        self._remote_read()
        return self.buffer.read(size)

    def readline(self, size=-1):
        self._remote_read()
        return self.buffer.readline(size)

    def readlines(self, sizehint=-1):
        self._remote_read()
        return self.buffer.readlines(sizehint)

    def xreadlines(self):
        self._remote_read()
        return self.buffer

    def seek(self, offset, whence=os.SEEK_SET):
        self.buffer.seek(offset, whence)
        # if it looks like we are moving in the file and we have not written
        # anything then we probably should read the contents
        if self.tell() != 0 and self._readreq and not self._writereq:
                self._remote_read()
                self.buffer.seek(offset, whence)

    def tell(self):
        return self.buffer.tell()

    def truncate(self, size=None):
        self._writereq = True
        self.buffer.truncate(size or self.tell())

    def write(self, s):
        self._writereq = True
        self.buffer.write(s)

    def writelines(self, sequence):
        self._writereq = True
        self.buffer.writelines(sequence)
Example #3
0
class FortranReader(object):
    """
    An iterator which will convert a free-form Fortran source file into
    a format more conducive for analyzing. It does the following:
        - combine line continuations into one
        - remove any normal comments and any comments following an ampersand
          (line continuation)
        - if there are documentation comments preceding a piece of code, buffer
          them and return them after the code, but before any documentation
          following it
        - keep any documentation comments and, if they are at the end of a line
          of actual code, place them on a new line
        - removes blank lines and trailing white-space
        - split lines along semicolons
    """

    # Regexes
    COM_RE = re.compile("^([^\"'!]|(\'[^']*')|(\"[^\"]*\"))*(!.*)$")
    SC_RE = re.compile("^([^;]*);(.*)$")

    def __init__(self,filename,docmark='!',predocmark='',docmark_alt='',
                 predocmark_alt='',preprocess=False,macros=[],inc_dirs=[]):
        self.name = filename
        
        # Check that none of the docmarks are the same
        if docmark == predocmark != '':
            raise Exception('Error: docmark and predocmark are the same.')
        if docmark == docmark_alt != '':
            raise Exception('Error: docmark and docmark_alt are the same.')
        if docmark == predocmark_alt != '':
            raise Exception('Error: docmark and predocmark_alt are the same.')
        if docmark_alt == predocmark != '':
            raise Exception('Error: docmark_alt and predocmark are the same.')
        if docmark_alt == predocmark_alt != '':
            raise Exception('Error: docmark_alt and predocmark_alt are the same.')
        if predocmark == predocmark_alt != '':
            raise Exception('Error: predocmark and predocmark_alt are the same.')
        
        if preprocess:
            macros = ['-D' + mac.strip() for mac in macros]
            incdirs = ['-I' + d.strip() for d in inc_dirs]
            fpp = subprocess.Popen(["gfortran", "-E", "-cpp", filename]+macros+incdirs, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            (out, err) = fpp.communicate()
            if len(err) > 0:
                print('Warning: error preprocessing '+filename)
                print(err)
                self.reader = open(filename,'r')
            else:
                self.reader = StringIO(str(out))
        else:
            self.reader = open(filename,'r')
        
        self.inc_dirs = inc_dirs
        self.docbuffer = []
        self.pending = []
        self.prevdoc = False
        self.reading_alt = 0
        self.docmark = docmark
        self.doc_re = re.compile("^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(re.escape(docmark)))
        self.predocmark = predocmark
        if len(self.predocmark) != 0:
            self.predoc_re = re.compile("^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(re.escape(predocmark)))
        else:
            self.predoc_re = None
        self.docmark_alt = docmark_alt
        if len(self.docmark_alt) != 0:
            self.doc_alt_re = re.compile("^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(re.escape(docmark_alt)))
        else:
            self.doc_alt_re = None
        self.predocmark_alt = predocmark_alt
        if len(self.predocmark_alt) != 0:
            self.predoc_alt_re = re.compile("^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(re.escape(predocmark_alt)))
        else:
            self.predoc_alt_re = None

    def __iter__(self):
        return self
        
    def pass_back(self,line):
        self.pending.insert(0,line)
    
    #for Python 2:
    def next(self):
        return self.__next__()
    
    def __next__(self):   # Python 3
        # If there are any lines waiting to be returned, return them
        if len(self.pending) != 0:
            self.include()
            self.prevdoc = False
            return self.pending.pop(0)
        # If there are any documentation lines waiting to be returned, return them.
        # This can happen for inline and preceding docs
        elif len(self.docbuffer) != 0:
            self.prevdoc = True
            return self.docbuffer.pop(0)
            
        # Loop through the source code until you have a complete line (including
        # all line continuations), or a complete preceding doc block
        done = False
        continued = False
        reading_predoc = False
        reading_predoc_alt = 0
        linebuffer = ""
        count = 0
        while not done:
            count += 1
            
            if (sys.version_info[0]>2):
                line = self.reader.__next__()
            else:   #Python 2
                line = self.reader.next()

            if len(line.strip()) > 0 and line.strip()[0] == '#': continue

            # Capture any preceding documenation comments
            if self.predoc_re:
                match = self.predoc_re.match(line)
            else:
                match = False
            if match:
                # Switch to predoc: following comment lines are predoc until the end of the block
                reading_predoc = True
                self.reading_alt = 0
                readeing_predoc_alt = 0
                # Substitute predocmark with docmark
                tmp = match.group(4)
                tmp = tmp[:1] + self.docmark + tmp[1+len(self.predocmark):]
                self.docbuffer.append(tmp)
                if len(line[0:match.start(4)].strip()) > 0:
                    raise Exception("Preceding documentation lines can not be inline")

            # Check for alternate preceding documentation
            if self.predoc_alt_re:
                match = self.predoc_alt_re.match(line)
            else:
                match = False
            if match:
                # Switch to doc_alt: following comment lines are documentation until end of the block
                reading_predoc_alt = 1
                self.reading_alt = 0
                reading_predoc = False
                # Substitute predocmark_alt with docmark
                tmp = match.group(4)
                tmp = tmp[:1] + self.docmark + tmp[1+len(self.predocmark_alt):]
                self.docbuffer.append(tmp)
                if len(line[0:match.start(4)].strip()) > 0:
                    raise Exception("Alternate documentation lines can not be inline")

            # Check for alternate succeeding documentation
            if self.doc_alt_re:
                match = self.doc_alt_re.match(line)
            else:
                match = False
            if match:
                # Switch to doc_alt: following comment lines are documentation until end of the block
                self.reading_alt = 1
                reading_predoc = False
                reading_predoc_alt = 0
                # Substitute predocmark_alt with docmark
                tmp = match.group(4)
                tmp = tmp[:1] + self.docmark + tmp[1+len(self.docmark_alt):]
                self.docbuffer.append(tmp)
                if len(line[0:match.start(4)].strip()) > 0:
                    raise Exception("Alternate documentation lines can not be inline")

            # Capture any documentation comments
            match = self.doc_re.match(line)
            if match:
                self.reading_alt = 0
                reading_predoc_alt = 0
                self.docbuffer.append(match.group(4))
                line = line[0:match.start(4)]

            if len(line.strip()) == 0 or line.strip()[0] != '!':
                self.reading_alt = 0

            if len(line.strip()) !=0 and line.strip()[0] != '!':
                reading_predoc_alt = 0

            # Remove any regular comments, unless following an alternative (pre)docmark
            match = self.COM_RE.match(line)
            if match:
                if (reading_predoc_alt > 1 or self.reading_alt > 1) and len(line[0:match.start(4)].strip()) == 0:
                    tmp = match.group(4)
                    tmp = tmp[:1] + self.docmark + tmp[1:]
                    self.docbuffer.append(tmp)
                line = line[0:match.start(4)]
            line = line.strip()

            # If this is a blank line following previous documentation, return
            # a line of empty documentation.
            if len(line) == 0:
                if self.prevdoc and len(self.docbuffer) == 0:
                    #~ self.prevdoc = False
                    self.docbuffer.append("!"+self.docmark)
            else:
                reading_predoc = False
                reading_predoc_alt = 0
                self.reading_alt = 0
                # Check if line is immediate continuation of previous
                if line[0] == '&':
                    if continued:
                        line = line[1:]
                    else:
                        raise Exception("Can not start a new line in Fortran with \"&\"")
                else:
                    linebuffer = linebuffer.strip()
                # Check if line will be continued
                if line[-1] == '&':
                    continued = True
                    line = line[0:-1]
                else:
                    continued = False

            if self.reading_alt > 0:
                self.reading_alt += 1
            
            if reading_predoc_alt > 0:
                reading_predoc_alt += 1

            # Add this line to the buffer then check whether we're done here
            linebuffer += line
            #~ print(((len(self.docbuffer) > 0) or (len(linebuffer) > 0)), not continued, not reading_predoc, (reading_predoc_alt == 0))
            done = ( ((len(self.docbuffer) > 0) or (len(linebuffer) > 0)) and
                     not continued and not reading_predoc and (reading_predoc_alt == 0) )

        # Split buffer with semicolons
        #~ print(count,linebuffer,len(linebuffer))
        frags = ford.utils.quote_split(';',linebuffer)
        self.pending.extend([ s.strip() for s in frags if len(s) > 0])
        
        # Return the line
        if len(self.pending) > 0:
            self.include()
            self.prevdoc = False
            return self.pending.pop(0)
        else:
            tmp = self.docbuffer.pop(0)
            if tmp != "!"+self.docmark:
                self.prevdoc = True;
            return tmp

    def include(self):
        """
        If the next line is an include statement, inserts the contents
        of the included file into the pending buffer.
        """
        if len(self.pending) == 0 or not self.pending[0].startswith('include '):
            return
        name = self.pending.pop(0)[8:].strip()[1:-1]
        for b in [os.path.dirname(self.name)] + self.inc_dirs:
            pname = os.path.join(b, name)
            if os.path.isfile(pname):
                name = pname
                break
        else:
            raise Exception('Can not find include file "{}".'.format(name))
        self.pending = list(FortranReader(name, self.docmark, self.predocmark, 
                                          self.docmark_alt, self.predocmark_alt,
                                          inc_dirs=self.inc_dirs)) \
                       + self.pending
Example #4
0
class FortranReader(object):
    """
    An iterator which will convert a free-form Fortran source file into
    a format more conducive for analyzing. It does the following:
        - combine line continuations into one
        - remove any normal comments and any comments following an ampersand
          (line continuation)
        - if there are documentation comments preceding a piece of code, buffer
          them and return them after the code, but before any documentation
          following it
        - keep any documentation comments and, if they are at the end of a line
          of actual code, place them on a new line
        - removes blank lines and trailing white-space
        - split lines along semicolons
    """

    # Regexes
    COM_RE = re.compile("^([^\"'!]|(\'[^']*')|(\"[^\"]*\"))*(!.*)$")
    SC_RE = re.compile("^([^;]*);(.*)$")

    #TODO: Add checks that there are no conflicts between docmark, predocmark, alt marks etc.
    def __init__(self,
                 filename,
                 docmark='!',
                 predocmark='',
                 docmark_alt='',
                 predocmark_alt='',
                 preprocess=False,
                 macros=[]):
        self.name = filename

        if preprocess:
            macros = ['-D' + mac.strip() for mac in macros]
            fpp = subprocess.Popen(["gfortran", "-E", "-cpp", filename] +
                                   macros,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            (out, err) = fpp.communicate()
            if len(err) > 0:
                print('Warning: error preprocessing ' + filename)
                self.reader = open(filename, 'r')
            else:
                self.reader = StringIO(str(out))
        else:
            self.reader = open(filename, 'r')

        self.docbuffer = []
        self.pending = []
        self.prevdoc = False
        self.reading_alt = 0
        self.docmark = docmark
        self.doc_re = re.compile(
            "^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(
                re.escape(docmark)))
        self.predocmark = predocmark
        if len(self.predocmark) != 0:
            self.predoc_re = re.compile(
                "^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(
                    re.escape(predocmark)))
        else:
            self.predoc_re = None
        self.docmark_alt = docmark_alt
        if len(self.docmark_alt) != 0:
            self.doc_alt_re = re.compile(
                "^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(
                    re.escape(docmark_alt)))
        else:
            self.doc_alt_re = None
        self.predocmark_alt = predocmark_alt
        if len(self.predocmark_alt) != 0:
            self.predoc_alt_re = re.compile(
                "^([^\"'!]|('[^']*')|(\"[^\"]*\"))*(!{}.*)$".format(
                    re.escape(predocmark_alt)))
        else:
            self.predoc_alt_re = None

    def __iter__(self):
        return self

    def pass_back(self, line):
        self.pending.insert(0, line)

    #for Python 2:
    def next(self):
        return self.__next__()

    def __next__(self):  # Python 3
        # If there are any lines waiting to be returned, return them
        if len(self.pending) != 0:
            self.prevdoc = False
            return self.pending.pop(0)
        # If there are any documentation lines waiting to be returned, return them.
        # This can happen for inline and preceding docs
        elif len(self.docbuffer) != 0:
            self.prevdoc = True
            return self.docbuffer.pop(0)

        # Loop through the source code until you have a complete line (including
        # all line continuations), or a complete preceding doc block
        done = False
        continued = False
        reading_predoc = False
        reading_predoc_alt = 0
        linebuffer = ""
        count = 0
        while not done:
            count += 1

            if (sys.version_info[0] > 2):
                line = self.reader.__next__()
            else:  #Python 2
                line = self.reader.next()

            if len(line.strip()) > 0 and line.strip()[0] == '#': continue

            # Capture any preceding documenation comments
            if self.predoc_re:
                match = self.predoc_re.match(line)
            else:
                match = False
            if match:
                # Switch to predoc: following comment lines are predoc until the end of the block
                reading_predoc = True
                self.reading_alt = 0
                readeing_predoc_alt = 0
                # Substitute predocmark with docmark
                tmp = match.group(4)
                tmp = tmp[:1] + self.docmark + tmp[1 + len(self.predocmark):]
                self.docbuffer.append(tmp)
                if len(line[0:match.start(4)].strip()) > 0:
                    raise Exception(
                        "Preceding documentation lines can not be inline")

            # Check for alternate preceding documentation
            if self.predoc_alt_re:
                match = self.predoc_alt_re.match(line)
            else:
                match = False
            if match:
                # Switch to doc_alt: following comment lines are documentation until end of the block
                reading_predoc_alt = 1
                self.reading_alt = 0
                reading_predoc = False
                # Substitute predocmark_alt with docmark
                tmp = match.group(4)
                tmp = tmp[:1] + self.docmark + tmp[1 +
                                                   len(self.predocmark_alt):]
                self.docbuffer.append(tmp)
                if len(line[0:match.start(4)].strip()) > 0:
                    raise Exception(
                        "Alternate documentation lines can not be inline")

            # Check for alternate succeeding documentation
            if self.doc_alt_re:
                match = self.doc_alt_re.match(line)
            else:
                match = False
            if match:
                # Switch to doc_alt: following comment lines are documentation until end of the block
                self.reading_alt = 1
                reading_predoc = False
                reading_predoc_alt = 0
                # Substitute predocmark_alt with docmark
                tmp = match.group(4)
                tmp = tmp[:1] + self.docmark + tmp[1 + len(self.docmark_alt):]
                self.docbuffer.append(tmp)
                if len(line[0:match.start(4)].strip()) > 0:
                    raise Exception(
                        "Alternate documentation lines can not be inline")

            # Capture any documentation comments
            match = self.doc_re.match(line)
            if match:
                self.reading_alt = 0
                reading_predoc_alt = 0
                self.docbuffer.append(match.group(4))
                line = line[0:match.start(4)]

            if len(line.strip()) == 0 or line.strip()[0] != '!':
                self.reading_alt = 0

            if len(line.strip()) != 0 and line.strip()[0] != '!':
                reading_predoc_alt = 0

            # Remove any regular comments, unless following an alternative (pre)docmark
            match = self.COM_RE.match(line)
            if match:
                if (reading_predoc_alt > 1 or self.reading_alt > 1) and len(
                        line[0:match.start(4)].strip()) == 0:
                    tmp = match.group(4)
                    tmp = tmp[:1] + self.docmark + tmp[1:]
                    self.docbuffer.append(tmp)
                line = line[0:match.start(4)]
            line = line.strip()

            # If this is a blank line following previous documentation, return
            # a line of empty documentation.
            if len(line) == 0:
                if self.prevdoc and len(self.docbuffer) == 0:
                    #~ self.prevdoc = False
                    self.docbuffer.append("!" + self.docmark)
            else:
                reading_predoc = False
                reading_predoc_alt = 0
                self.reading_alt = 0
                # Check if line is immediate continuation of previous
                if line[0] == '&':
                    if continued:
                        line = line[1:]
                    else:
                        raise Exception(
                            "Can not start a new line in Fortran with \"&\"")
                else:
                    linebuffer = linebuffer.strip()
                # Check if line will be continued
                if line[-1] == '&':
                    continued = True
                    line = line[0:-1]
                else:
                    continued = False

            if self.reading_alt > 0:
                self.reading_alt += 1

            if reading_predoc_alt > 0:
                reading_predoc_alt += 1

            # Add this line to the buffer then check whether we're done here
            linebuffer += line
            #~ print(((len(self.docbuffer) > 0) or (len(linebuffer) > 0)), not continued, not reading_predoc, (reading_predoc_alt == 0))
            done = (((len(self.docbuffer) > 0) or (len(linebuffer) > 0))
                    and not continued and not reading_predoc
                    and (reading_predoc_alt == 0))

        # Split buffer with semicolons
        #~ print(count,linebuffer,len(linebuffer))
        frags = ford.utils.quote_split(';', linebuffer)
        self.pending.extend([s.strip() for s in frags if len(s) > 0])

        # Return the line
        if len(self.pending) > 0:
            self.prevdoc = False
            return self.pending.pop(0)
        else:
            tmp = self.docbuffer.pop(0)
            if tmp != "!" + self.docmark:
                self.prevdoc = True
            return tmp