Skip to content

Instantly share code, notes, and snippets.

@rafaqz
Last active March 17, 2024 21:34
Show Gist options
  • Save rafaqz/fede683a3e853f36c9b367471fde2f56 to your computer and use it in GitHub Desktop.
Save rafaqz/fede683a3e853f36c9b367471fde2f56 to your computer and use it in GitHub Desktop.
Fortran-Julia conversion script
#=
This julia script converts fortran 90 code into julia.
It uses naive regex replacements to do as much as possible,
but the output WILL need further cleanup.
Known conversion problems such as GOTO are commented and marked with FIXME
Most variable declaration lines are entirely deleted, which may or
may not be useful.
To run from a shell:
julia fortran-julia.jl filename.f90
Output is written to filename.jl.
=#
using DataStructures
# Regex/substitution pairs for replace(). Order matters here.
replacements = OrderedDict(
# Lowercase everything not commented
r"^(?!.*!).*"m => lowercase,
# Lowercase start of lines with comments
r"^.*!"m => lowercase,
# Remove '&' multiline continuations
r"\s*&\s*" => "",
# Comments use # not !
"!" => "#",
# Powers use ^ not **
"**" => "^",
# Only double quotes allowed for strings
"'" => "\"",
# DO loop to for loop
r"do (.*),(.*)" => s"for \1:\2",
# Spaces around math operators
r"([\*\+\/=])(?=\S)" => s"\1 ",
r"(?<=\S)([\*\+\/=])" => s" \1",
# Spaces around - operators, except after e
# r"([^e][\-])(\S)" => s"\1 \2",
r"(?<!\W\de)(\h*\-\h*)" => s" - ",
# Space after all commas
r"(,)(\S)" => s"\1 \2",
# Replace ELSEIF/ELSE IF with elseif
r"(\s+)else if" => s"\1elseif",
# Replace IF followed by ( to if (
r"(\s+)(elseif|if)\(" => s"\1\2 (",
# Remove THEN
r"([)\s])then(\s+)" => s"\1\2",
# Relace END XXXX with end
r"(\s+)end\h*.*" => s"\1end",
# Replace expnent function
r"(\W)exp\(" => s"\1exp(",
# Reorganise functions and doc strings. This may be very project specific.
r"#\^\^+\s*subroutine\s*(\w+)([^)]+\))\s*(.*?)#\^\^\^+"sm =>
Base.SubstitutionString("\"\"\"\n\\3\"\"\"\nfunction \\1\\2::Void"),
r"\#\^\^+\s*real function\s*(\w+)([^)]+\))\s*(.*?)\#\^\^\^+"sm =>
Base.SubstitutionString("\"\"\"\n\\3\"\"\"\nfunction \\1\\2::Float64"),
# Don't need CALL
r"(\s*)call(\h+)" => s"\1",
# Use real math symbols
"gamma" => "Γ",
"theta" => "Θ",
"epsilon" => "ϵ",
"lambda" => "λ",
"alpha" => "α",
# Swap logical symbols
".true." => "true",
".false." => "false",
r"\s*\.or\.\s*" => " || ",
r"\s*\.and\.\s*" => " && ",
r"\s*\.not\.\s*" => " ! ",
r"\s*\.eq\.\s*" => " == ",
r"\s*\.ne\.\s*" => " != ",
r"\s*\.le\.\s*" => " <= ",
r"\s*\.ge\.\s*" => " >= ",
r"\s*\.gt\.\s*" => " > ",
r"\s*\.lt\.\s*" => " < ",
# Remove (expression) brackets after if
# r"if \((.*)\)(\s*\n)" => s"if \1\2",
# Add end after single line if with an = assignment
r"if\s*(.*?) = (.*?)(\n)" => s"if \1 = \2 end\3",
# Format floats as "5.0" not "5."
r"(\W\d+)\.(\D)" => s"\1.0\2",
# Tab to 4 spaces
r"\t" => " ",
# Relace suberror with error and mark for fixup
r"(\W)suberror\((.*?),.*?\)" => s"\1 error(\2)",
# Mark #FIXME the various things this script can't handle
r"(write|goto|while\s)" => s"#FIXME \1",
)
# Patterns to remove
removal = [
# Trailing whitespace
r"\h*$"m,
# Variable declarations
r"\n\s*real\s.*",
r"\n\s*real, external\s.*",
r"\n\s*integer\s.*",
r"\n\s*implicit none",
r"\n\s*logical\s.*",
# Import statements
r"\n\s*use\s.*",
]
# Load the file from the first command line argument
filename = string(ARGS[1])
global code = read(filename, String)
# Process replacements and removals.
for (f, r) in replacements
global code = replace(code, f, r)
end
for r in removal
global code = replace(code, r, "")
end
println(code)
# Write the output to a .jl file with the same filename stem.
stem = split(filename, ".")[1]
outfile = stem * ".jl"
write(outfile, code)
#=
Copyright (c) 2022 Rafael Schouten
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=#
@akshayb29
Copy link

Hi, I'm looking to convert an F90 code to support Julia v1.1. Do you have suggestions on that or can you direct me on how I could go about the process using this code as a base?

@rafaqz
Copy link
Author

rafaqz commented Apr 25, 2020

Try running this script on some of your files and see what you get. The comments document what everything does, so just add or remove lines to your needs.

@rafaqz
Copy link
Author

rafaqz commented Apr 25, 2020

Be careful changing the order, it does matter which things are replaced first

@baptiste
Copy link

baptiste commented Dec 3, 2021

in julia 1.6 I had to replace

# code = readstring(filename)
global code = read(filename, String)

as well as

for f in replacements
  global code = replace(code, f)
end

(I also commented out the removals, which I preferred to keep for manual checks)

@ViralBShah
Copy link

Probably should make this into a package.

@rafaqz
Copy link
Author

rafaqz commented Jun 9, 2022

Its honestly a regex hack I havent used since 2019... there will be edge cases and version of Fortran it really won't work on, and I'm concerned that would generate a lot of maintenance. But if someone wants to bundle it into a package go for it.

@ViralBShah
Copy link

Makes sense.

@4SAnalyticsnModelling
Copy link

4SAnalyticsnModelling commented Oct 6, 2022

I have used your script as a base and modified it to successfully convert a f77 legacy code base (about 9000 pages long) to Julia scripts. Pretty awesome, thank you @rafaqz for creating and sharing your script!

@jrocha72
Copy link

jrocha72 commented Oct 17, 2022

Hi,
Got this "ERROR: LoadError: UndefVarError: readstring not defined" when tried to run the code.
Any suggestion?
Thanks

@rafaqz
Copy link
Author

rafaqz commented Oct 17, 2022

Look at the comment above fixing this problem ;)

@jrocha72
Copy link

Thanks @rafaqz

@jrocha72
Copy link

Hi @rafaqz, i was trying to understand how to define/declare the scope of a variable. I'm used to fortran but i'm having some trouble to understand how to define the scope of a local "i" in a for loop, i got this: "UndefVarError: i not defined". I want to run a basic application of lagrange interpolation.
Thanks in advance.

@rafaqz
Copy link
Author

rafaqz commented Oct 19, 2022

This is a good question for the Julia discourse! Read this https://discourse.julialang.org/t/please-read-make-it-easier-to-help-you/14757 and make a post including a minimum working example of your code and people will help you.

@jrocha72
Copy link

jrocha72 commented Oct 19, 2022 via email

@rafaqz
Copy link
Author

rafaqz commented Oct 19, 2022

I have also updated the gist to include the advice from the comment, so it should work now without change

@dmey
Copy link

dmey commented Oct 27, 2022

@rafaqz could you please add a licence header (MIT?) so I can include this in another project and credit you?

@rafaqz
Copy link
Author

rafaqz commented Oct 30, 2022

@dmey I added an MIT license to the text

@dmey
Copy link

dmey commented Oct 30, 2022

@dmey I added an MIT license to the text

@rafaqz thanks!

@KapilKhanal
Copy link

@rafaqz I am getting error ERROR: LoadError: MethodError: no method matching replace(::String, ::Regex, ::typeof(lowercase)) I am using Julia1.7

@KapilKhanal
Copy link

I was able to solve simply by switching to replace(code, f=> r)

@denius
Copy link

denius commented Jan 8, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment