Skip to content
Snippets Groups Projects
verify_mnh_expand.py 7.13 KiB
Newer Older
#!/usr/bin/env python3

import os
import glob
import logging

def verify_mnh_expand(path):
    """
    Verifies if source files are ready for expansion through mnh_expand
    :param path: directory to recursively check or file name

    Presently the folowing tests are performed:
      - starting and closing directives are conform
      - each instruction in the bloc is an effectation to an array with the right number of dimensions

    Limitation:
      - if the '=' sign is not on same line than the left hand side of the affectation instruction,
        the instruction will no be checked
      - one-line version of IF or WHERE statement are really on one line (no continuation line)
    """
    lhschar = b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890(:)%, \t'

    if os.path.isdir(path):
        logging.info("Enters directory: " + path)
        for filename in glob.glob(os.path.join(path, '*')):
            verify_mnh_expand(filename)
    else:
        logging.debug("Checks filename: " + path)
        with open(path, 'rb') as f: #read as byte because some files contain non UTF-8 characters
            lines = f.readlines()
        inside = False
        for iline, line in enumerate(lines):
            line = line.strip()
            if line[:13] == b'!$mnh_expand_':
                #New mnh_expand bloc
                logging.debug('Opening directive found. Line {line} of file {filename}'.format(line=iline + 1, filename=path))
                if inside:
                    logging.error('New mnh_expand bloc detected whereas we are already in a bloc. ' +
                                  'Line {line} of file {filename}'.format(line=iline + 1, filename=path))
                inside = True
                open_directive = line[13:].split(b'(')[0]
                open_args = line[13:].split(b'(')[1].split(b')')[0].replace(b' ', b'')
                dim = len(line.split(b'(')[1].split(b','))
            elif line[:17] == b'!$mnh_end_expand_':
                #End of a mnh_expand bloc
                logging.debug('Closing directive found. Line {line} of file {filename}'.format(line=iline + 1, filename=path))
                if not inside:
                    logging.error('End of a mnh_expand bloc detected whereas we are not in a bloc. ' +
                                  'Line {line} of file {filename}'.format(line=iline + 1, filename=path))
                else:
                    inside = False
                    end_directive = line[17:].split(b'(')[0]
                    end_args = line[17:].split(b'(')[1].split(b')')[0].replace(b' ', b'')
                    if end_directive != open_directive:
                        logging.error('The end directive ({enddirect}) is not consistent with the opening directive ({opendirect}). '
                                      'Line {line} of file {filename}'.format(enddirect=end_directive.decode('UTF-8'),
                                                                              opendirect=open_directive.decode('UTF-8'),
                                                                              line=iline + 1, filename=path))
                    if end_args.upper() != open_args.upper():
                        logging.error('The end args ({endargs}) are not consistent with the opening args ({openargs}). '
                                      'Line {line} of file {filename}'.format(endargs=end_args.decode('UTF-8'),
                                                                              openargs=open_args.decode('UTF-8'),
                                                                              line=iline + 1, filename=path))
            elif inside:
                #We do not want to implement a full fortran parser, we are only interested in the left hand side of
                #affectation instructions. If left hand side is correct (an array element) the right hand side
                #will be necessarily correct (otherwise a compilation error will be thrown).

                #Suppresion of condition in 'IF' and 'WHERE' one-line instructions
                if line[:3].upper() in(b'IF ', b'IF('):
                    line = line[line.index(b'(') + 1:]
                    nb = 1
                    while nb >= 1 and(len(line) > 0):
                        if line[:1] == b'(': nb += 1
                        elif line[:1] == b')': nb -= 1
                        line = line[1:].strip()
                    if line.upper()[:5] in (b'THEN ', b'THEN!'): line = line[5:]
                elif line[:6].upper() in(b'WHERE ', b'WHERE('):
                    line = line[line.index(b'(') + 1:]
                    nb = 1
                    while nb >= 1 and(len(line)>0):
                        if line[:1] == b'(': nb += 1
                        elif line[:1] == b')': nb -= 1
                        line = line[1:].strip()

                #Check if it is the left hand side of an affectation
                if line[:3].upper() == b'DO ':
                    logging.warning('A DO loop is inside a mnh_expand bloc, is order correct?. ' +
                                    'Line {line} of file {filename}'.format(line=iline + 1, filename=path))
                elif line[:5].upper() == b'CALL ':
                    logging.warning('A CALL statement is inside a mnh_expand bloc, is it correct? ' +
                                    'Line {line} of file {filename}'.format(line=iline + 1, filename=path))
                elif b'=' in line and all([c in lhschar for c in line.split(b'=')[0]]):
                    lhs = line.split(b'=')[0]
                    if not b'(' in lhs:
                        logging.error('Array on the left hand side of an effectation instruction must be written ' +
                                      'with opening and closing brackets. ' + 
                                      'Line {line} of file {filename}'.format(line=iline + 1, filename=path))
                    if lhs.count(b':') != dim:
                        logging.error('Array on the left hand side of an effectation instruction must have the same ' +
                                      'number of :-dimensions as the number defined in the directive. ' +
                                      'Line {line} of file {filename}'.format(line=iline + 1, filename=path))
                    if line[line.index(b'(')-1] in b' \t':
                        logging.error('There must be no space wetween the array name and the opening bracket in ' +
                                      'the affectation instruction. ' +
                                      'Line {line} of file {filename}'.format(line=iline + 1, filename=path))

if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(description='mnh_expand checker')
    parser.add_argument("-v", "--verbose", dest="verbose", action="count", default=0,
                        help="Show warning (-v), info (-v -v) or debug (-v -v -v) messages")
    parser.add_argument('PATH', help="directory to recursively check, or filename")
    args = parser.parse_args()
    level = {0:'ERROR',
             1:'WARNING',
             2:'INFO',
             3:'DEBUG'}[args.verbose]
    logging.basicConfig(level=getattr(logging, level, None))
    verify_mnh_expand(args.PATH)