Skip to content

Instantly share code, notes, and snippets.

@rafaqz
Last active March 17, 2024 21:34
Show Gist options
  • Select an option

  • Save rafaqz/fede683a3e853f36c9b367471fde2f56 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

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

rafaqz commented Apr 25, 2020

Copy link
Copy Markdown
Author

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

rafaqz commented Apr 25, 2020

Copy link
Copy Markdown
Author

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

@baptiste

baptiste commented Dec 3, 2021

Copy link
Copy Markdown

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
Copy Markdown

Probably should make this into a package.

@rafaqz

rafaqz commented Jun 9, 2022

Copy link
Copy Markdown
Author

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
Copy Markdown

Makes sense.

@4SAnalyticsnModelling

4SAnalyticsnModelling commented Oct 6, 2022

Copy link
Copy Markdown

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

jrocha72 commented Oct 17, 2022

Copy link
Copy Markdown

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

@rafaqz

rafaqz commented Oct 17, 2022

Copy link
Copy Markdown
Author

Look at the comment above fixing this problem ;)

@jrocha72

Copy link
Copy Markdown

Thanks @rafaqz

@jrocha72

Copy link
Copy Markdown

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

rafaqz commented Oct 19, 2022

Copy link
Copy Markdown
Author

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

jrocha72 commented Oct 19, 2022 via email

Copy link
Copy Markdown

@rafaqz

rafaqz commented Oct 19, 2022

Copy link
Copy Markdown
Author

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

@dmey

dmey commented Oct 27, 2022

Copy link
Copy Markdown

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

@rafaqz

rafaqz commented Oct 30, 2022

Copy link
Copy Markdown
Author

@dmey I added an MIT license to the text

@dmey

dmey commented Oct 30, 2022

Copy link
Copy Markdown

@dmey I added an MIT license to the text

@rafaqz thanks!

@KapilKhanal

Copy link
Copy Markdown

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

@KapilKhanal

Copy link
Copy Markdown

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

@denius

denius commented Jan 8, 2023

Copy link
Copy Markdown

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