#!/usr/bin/env python3

# (C) 2010 Norbert Nemec
#
# USAGE: src-normal.py < input.f90 > output.f90
#
# Script to normalize Fortran source code:
#   a) expand tabs to spaces (tab width 8 characters)
#   b) remove trailing space
#   c) normalize multiword keywords
#   d) normalize capitalization of keywords and intrinsics
#   e) replace old relational operators (.eq., .gt., etc.) by new ones (==, >, etc.)
# The script skips comments and strings within the code

import sys,re

dropspace_list = [
 "BLOCK *DATA",
 "CASE *DEFAULT",     # SPLIT NOT OPTIONAL !
 "DOUBLE *PRECISION",
 "DO *WHILE",         # SPLIT NOT OPTIONAL !
 "ELSE *IF",
 "END *BLOCK *DATA",
 "END *DO",
 "END *FILE",
 "END *FORALL",
 "END *FUNCTION",
 "END *IF",
 "END *INTERFACE",
 "END *MODULE",
 "END *PROGRAM",
 "END *SELECT",
 "END *SUBROUTINE",
 "END *TYPE",
 "END *WHERE",
 "GO *TO",
 "IN *OUT",
 "MODULE *PROCEDURE", # SPLIT NOT OPTIONAL !
 "SELECT *CASE",
]

splitword_list = [
 "BLOCK DATA",
 "CASE DEFAULT", # SPLIT NOT OPTIONAL
 "DOUBLE PRECISION",
 "DO WHILE", # SPLIT NOT OPTIONAL
# "ELSEIF", # leave as one word
 "END BLOCK DATA",
# "ENDDO",  # leave as one word
 "END FILE",
 "END FORALL",
 "END FUNCTION",
# "ENDIF",  # leave as one word
 "END INTERFACE",
 "END MODULE",
 "END PROGRAM",
 "END SELECT",
 "END SUBROUTINE",
 "END TYPE",
 "END WHERE",
# "GOTO",   # leave as one word
# "INOUT",  # leave as one word
 "MODULE PROCEDURE", # SPLIT NOT OPTIONAL
 "SELECT CASE",
]


dropspace_re = re.compile(r"\b("+"|".join(dropspace_list)+r")\b",re.I)

def dropspace_fn(s):
    return s.group(0).replace(" ","")

splitword_dict = dict( (a.replace(" ","").lower(),a) for a in splitword_list )
splitword_re = re.compile(r"\b("+"|".join(splitword_list).replace(" ","")+r")\b",re.I)

def splitword_fn(s):
    return splitword_dict[s.group(0).lower()]


uppercase_keywords = r"""
MODULE SUBROUTINE PROGRAM FUNCTION INTERFACE
ENDMODULE ENDSUBROUTINE ENDPROGRAM ENDFUNCTION ENDINTERFACE
BLOCKDATA DOUBLEPRECISION
MODULEPROCEDURE
TYPE ENDTYPE
CONTAINS
USE ONLY
ALLOCATABLE DIMENSION INTENT EXTERNAL INTRINSIC OPTIONAL PARAMETER POINTER
COMMON
FORMAT
IMPLICIT NONE
PRIVATE PUBLIC
CHARACTER COMPLEX INTEGER LOGICAL
ENTRY EQUIVALENCE INCLUDE NAMELIST SAVE SEQUENCE TARGET
ELEMENTAL PURE RECURSIVE RESULT

SELECTCASE CASE CASEDEFAULT ENDSELECT
IF THEN ELSEIF ELSE ENDIF
WHERE ELSEWHERE ENDWHERE
FORALL ENDFORALL
DO DOWHILE ENDDO

ALLOCATE ASSIGN BACKSPACE CALL CLOSE CONTINUE CYCLE DEALLOCATE ENDFILE
EXIT FORMAT GOTO INQUIRE NULLIFY OPEN PAUSE PRINT READ RETURN REWIND STOP WRITE

""".split()

lowercase_keywords = r"""
in inout out
""".split()

intrinsics = r"""
abort abs achar acos acosd acosh adjustl adjustr aimag aint all allocated and anint any asin
asind asinh associated atan atan2 atan2d atand atanh
baddress bit_size btest
ceiling char cmplx conjg cos cosd cosh count cshift
date date_and_time dble dcmplx dfloat digits dim dnum dot_product dprod dreal
eoshift epsilon exit exp exponent
floor flush fnum fraction free fset fstream
getarg getenv gran
hfix huge
iachar iaddr iand iargc ibclr ibits ibset ichar idate idim ieor igetarg ijint imag index int int1
int2 int4 int8 inum iomsg ior iqint irand iranp ishft ishftc isign ixor izext
jnum jzext
kind kzext
lbound len len_trim lge lgt lle llt loc log log10 lshft lshift
malloc matmul max maxexponent maxloc maxval mclock merge min minexponent minloc minval mod modulo mvbits
nearest nint not
or
pack precision present product
qext qfloat qnum qprod
radix ran rand random_number random_seed range repeat reshape rnum rrspacing rshft rshift
scale scan secnds selected_int_kind selected_real_kind set_exponent shape sign sin sind sinh size
sizeof spacing spread sqrt srand sum system system_clock
tan tand tanh time tiny transfer transpose trim
ubound unpack
verify xor zext
""".split()

ignore_for_the_moment = r"""
real REAL isnan
"""

special_keywords = r"""
.and. .or. .not. .true. .false. .eqv. .neqv.
.eq. .ge. .gt. .le. .lt. .ne.
""".replace(".","\\.").split()

def uppercase_fn(s):
    return s.group(0).upper()

def lowercase_fn(s):
    return s.group(0).lower()

def special_fn(s):
    res = s.group(0).lower()
    res = {
    '.eq.': '==',
    '.ge.': '>=',
    '.gt.': '>',
    '.le.': '<=',
    '.lt.': '<',
    '.ne.': '/=',
    }.get(res,res)
    return res

uppercase_re = re.compile(r"\b("+"|".join(uppercase_keywords)+r")\b",re.I)
lowercase_re = re.compile(r"\b("+"|".join(lowercase_keywords+intrinsics)+r")\b",re.I)
special_re = re.compile(r"("+"|".join(special_keywords)+r")",re.I)

def correctcase(line):
    line = dropspace_re.sub(dropspace_fn,line)
    line = uppercase_re.sub(uppercase_fn,line)
    line = lowercase_re.sub(lowercase_fn,line)
    line = special_re.sub(special_fn,line)
    line = splitword_re.sub(splitword_fn,line)
    return line

##############

quote = " "
QUOTES = "'\""

for lin in sys.stdin:
    lin = lin.rstrip().expandtabs()
    pos = 0
    lout = ""
    if lin[:1] == "#":
        lout=lin
        pos=len(lin)
    while pos < len(lin):
        if quote in QUOTES:
            npos = lin.find(quote,pos)
            if npos >= 0:
                assert lin[npos] == quote
                lout += lin[pos:npos+1]
                pos = npos+1
                quote = " "
            elif lin[-1] == "&":
                lout += lin[pos:]
                break
            else:
                raise "unterminated string in line ["+lin+"]"

        cpos = lin.find("!",pos) % (len(lin)+1)
        qpos = lin.find("'",pos) % (len(lin)+1)
        dpos = lin.find('"',pos) % (len(lin)+1)
        npos = min(cpos,qpos,dpos)
        lout += correctcase(lin[pos:npos])
        pos = npos
        if pos == len(lin):
            break
        elif lin[pos] == "!":
            lout += lin[pos:]
            break
        elif lin[pos] in QUOTES:
            quote = lin[pos]
            lout += quote
            pos += 1
            continue
        else:
            raise "Strange internal error"

    sys.stdout.write(lout+"\n")
