# Fix script that translates Fortran to Julia

**URL:** <https://discourse.julialang.org/t/fix-script-that-translates-fortran-to-julia/71748>\
**Category:** New to Julia\
**Created:** [November 18, 2021, 9:49pm UTC](https://discourse.julialang.org/t/fix-script-that-translates-fortran-to-julia/71748 "2021-11-18T21:49:38Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![Beliavsky](https://avatars.discourse-cdn.com/v4/letter/b/ba8739/32.png) [@Beliavsky](https://discourse.julialang.org/u/Beliavsky)\
**Post date:** [November 18, 2021, 9:49pm UTC](https://discourse.julialang.org/t/fix-script-that-translates-fortran-to-julia/71748/1 "2021-11-18T21:49:38Z")

</div>

Running with Julia 1.6.1 the script

```julia
#=
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 = ARGS[1]
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)

```

slightly modified from [here](https://gist.github.com/rafaqz/fede683a3e853f36c9b367471fde2f56)  
with `julia fortran-julia.jl xhi.f90` where `xhi.f90` is the Fortran program

```julia
program main
write (*,*) "hi"
end program main

```

I get an error

```julia
ERROR: LoadError: MethodError: no method matching replace(::String, ::Regex, ::typeof(lowercase))
Closest candidates are:
  replace(!Matched::Union{Function, Type}, ::Any; count) at set.jl:605
  replace(::String, !Matched::Pair{var"#s77", B} where {var"#s77"<:AbstractChar, B}; count) at strings/util.jl:513
  replace(::String, !Matched::Pair{var"#s74", B} where {var"#s74"<:Union{Tuple{Vararg{AbstractChar, N} where N}, Set{var"#s55"} where var"#s55"<:AbstractChar, AbstractVector{var"#s72"} where var"#s72"<:AbstractChar}, B}; count) at strings/util.jl:518
  ...
Stacktrace:
 [1] top-level scope
   @ c:\julia\fortran-julia.jl:108
in expression starting at c:\julia\fortran-julia.jl:107

```

Since modern Fortran Julia are somewhat similar languages, it would be nice if there were a working partial transpiler.

---

<div class="post-metadata">

**Author:** ![stillyslalom](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stillyslalom/32/45687_2.png) [@stillyslalom](https://discourse.julialang.org/u/stillyslalom)\
**Post date:** [November 18, 2021, 10:14pm UTC](https://discourse.julialang.org/t/fix-script-that-translates-fortran-to-julia/71748/2 "2021-11-18T22:14:09Z")

</div>

When converting pre-v1.0 Julia scripts to v1.0-compatible syntax, it’s helpful to run the script in v0.7, which will give you deprecation warnings:

```julia
julia> replace("asdf", r"", lowercase)
┌ Warning: `replace(s::AbstractString, pat, f)` is deprecated, use `replace(s, pat => f)` instead.
│ caller = top-level scope at none:0
└ @ Core none:0
"asdf"

```

…so in this case, you’ll change `replace(code, f, r)` to `replace(code, f => r)` and `replace(code, r, "")` to `replace(code, r => "")`.

---

<div class="post-metadata">

**Author:** ![Beliavsky](https://avatars.discourse-cdn.com/v4/letter/b/ba8739/32.png) [@Beliavsky](https://discourse.julialang.org/u/Beliavsky)\
**Post date:** [November 19, 2021, 2:32am UTC](https://discourse.julialang.org/t/fix-script-that-translates-fortran-to-julia/71748/3 "2021-11-19T02:32:32Z")

</div>

Thanks – with those changes the script works. For example, it transforms

> **Fortran code**
>
> ```julia
> module black_scholes_mod
> implicit none
> integer, parameter :: dp = kind(1.0d0)
> private
> public :: call_price,alnorm,cumnorm,print_call_price
> contains
> impure elemental subroutine print_call_price(s,k,r,t,vol)
> ! Black-Scholes price of a European call option
> real(kind=dp), intent(in) :: s ! stock price
> real(kind=dp), intent(in) :: k ! strike price
> real(kind=dp), intent(in) :: r ! annual interest rate -- 0.02 means 2%
> real(kind=dp), intent(in) :: t ! time to expiration in years
> real(kind=dp), intent(in) :: vol ! annualized volatility -- 0.30 means 30%
> write (*,"(*(f8.4))") s,k,r,t,vol,call_price(s,k,r,t,vol)
> end subroutine print_call_price
> 
> pure elemental function call_price(s,k,r,t,vol) result(price)
> ! Black-Scholes price of a European call option
> real(kind=dp), intent(in) :: s ! stock price
> real(kind=dp), intent(in) :: k ! strike price
> real(kind=dp), intent(in) :: r ! annual interest rate -- 0.02 means 2%
> real(kind=dp), intent(in) :: t ! time to expiration in years
> real(kind=dp), intent(in) :: vol ! annualized volatility -- 0.30 means 30%
> real(kind=dp) :: price ! call price
> real(kind=dp) :: d1,d2,vol_sqrt_t
> vol_sqrt_t = vol*sqrt(t)
> d1 = (log(s/k) + (r + 0.5_dp*vol**2)*t)/vol_sqrt_t
> d2 = d1 - vol_sqrt_t
> price = s*cumnorm(d1) - k*exp(-r*t)*cumnorm(d2) 
> end function call_price
> !
> ! Algorithm AS66 Applied Statistics (1973) vol.22, no.3
> 
> ! Evaluates the tail area of the standardised normal curve
> ! from x to infinity if upper is .true. or
> ! from minus infinity to x if upper is .false.
> 
> ! ELF90-compatible version by Alan Miller
> ! Latest revision - 29 November 2001
> pure elemental function alnorm(x, upper) result(fn_val)
> real(dp), intent(in) :: x
> logical , intent(in) :: upper
> real(dp) :: fn_val
> ! local variables
> real(dp), parameter :: zero=0.0_dp, one=1.0_dp, half=0.5_dp, con=1.28_dp
> real(dp) :: z, y
> logical :: up
> ! machine dependent constants
> real(dp), parameter :: ltone = 7.0_dp, utzero = 18.66_dp
> real(dp), parameter :: p = 0.398942280444_dp, q = 0.39990348504_dp, &
> r = 0.398942280385_dp, a1 = 5.75885480458_dp, &
> a2 = 2.62433121679_dp, a3 = 5.92885724438_dp, &
> b1 = -29.8213557807_dp, b2 = 48.6959930692_dp, &
> c1 = -3.8052e-8_dp, c2 = 3.98064794e-4_dp, &
> c3 = -0.151679116635_dp, c4 = 4.8385912808_dp, &
> c5 = 0.742380924027_dp, c6 = 3.99019417011_dp, &
> d1 = 1.00000615302_dp, d2 = 1.98615381364_dp, &
> d3 = 5.29330324926_dp, d4 = -15.1508972451_dp, &
> d5 = 30.789933034_dp
> up = upper
> z = x
> if (z < zero) then
> up = .not. up
> z = -z
> end if
> if (z <= ltone .or. (up .and. z <= utzero)) then
> y = half*z*z
> if (z > con) then
> fn_val = r*exp( -y )/(z+c1+d1/(z+c2+d2/(z+c3+d3/(z+c4+d4/(z+c5+d5/(z+c6))))))
> else
> fn_val = half - z*(p-q*y/(y+a1+b1/(y+a2+b2/(y+a3))))
> end if
> else
> fn_val = zero
> end if
> if (.not. up) fn_val = one - fn_val
> end function alnorm
> !
> pure elemental function cumnorm(x) result(fn_val)
> ! adapted from alnorm with upper = .false.
> real(dp), intent(in) :: x
> real(dp) :: fn_val
> ! local variables
> real(dp), parameter :: zero=0.0_dp, one=1.0_dp, half=0.5_dp, con=1.28_dp
> real(dp) :: z, y
> logical :: up
> ! machine dependent constants
> real(dp), parameter :: ltone = 7.0_dp, utzero = 18.66_dp
> real(dp), parameter :: p = 0.398942280444_dp, q = 0.39990348504_dp, &
> r = 0.398942280385_dp, a1 = 5.75885480458_dp, &
> a2 = 2.62433121679_dp, a3 = 5.92885724438_dp, &
> b1 = -29.8213557807_dp, b2 = 48.6959930692_dp, &
> c1 = -3.8052e-8_dp, c2 = 3.98064794e-4_dp, &
> c3 = -0.151679116635_dp, c4 = 4.8385912808_dp, &
> c5 = 0.742380924027_dp, c6 = 3.99019417011_dp, &
> d1 = 1.00000615302_dp, d2 = 1.98615381364_dp, &
> d3 = 5.29330324926_dp, d4 = -15.1508972451_dp, &
> d5 = 30.789933034_dp
> z = x
> if (z < zero) then
> up = .true.
> z = -z
> else
> up = .false.
> end if
> if (z <= ltone .or. (up .and. z <= utzero)) then
> y = half*z*z
> if (z > con) then
> fn_val = r*exp( -y )/(z+c1+d1/(z+c2+d2/(z+c3+d3/(z+c4+d4/(z+c5+d5/(z+c6))))))
> else
> fn_val = half - z*(p-q*y/(y+a1+b1/(y+a2+b2/(y+a3))))
> end if
> else
> fn_val = zero
> end if
> if (.not. up) fn_val = one - fn_val
> end function cumnorm
> !
> end module black_scholes_mod
> 
> ```

to

> **pseudo-Julia code**
>
> ```julia
> module black_scholes_mod
> integer, parameter :: dp = kind(1.0d0)
> private
> public :: call_price, alnorm, cumnorm, print_call_price
> contains
> impure elemental subroutine print_call_price(s, k, r, t, vol)
> # Black - Scholes price of a European option
> real(kind = dp), intent(in) :: s # stock price
> real(kind = dp), intent(in) :: k # strike price
> real(kind = dp), intent(in) :: r # annual interest rate - - 0.02 means 2%
> real(kind = dp), intent(in) :: t # time to expiration in years
> real(kind = dp), intent(in) :: vol # annualized volatility - - 0.30 means 30%
> #FIXME write ( * , "( * (f8.4))") s, k, r, t, vol, call_price(s, k, r, t, vol)
> end
> 
> pure elemental function call_price(s, k, r, t, vol) result(price)
> # Black - Scholes price of a European option
> real(kind = dp), intent(in) :: s # stock price
> real(kind = dp), intent(in) :: k # strike price
> real(kind = dp), intent(in) :: r # annual interest rate - - 0.02 means 2%
> real(kind = dp), intent(in) :: t # time to expiration in years
> real(kind = dp), intent(in) :: vol # annualized volatility - - 0.30 means 30%
> real(kind = dp) :: price # price
> real(kind = dp) :: d1, d2, vol_sqrt_t
> vol_sqrt_t = vol * sqrt(t)
> d1 = (log(s / k) + (r + 0.5_dp * vol^2) * t) / vol_sqrt_t
> d2 = d1 - vol_sqrt_t
> price = s * cumnorm(d1) - k * exp( - r * t) * cumnorm(d2) 
> end
> #
> # Algorithm AS66 Applied Statistics (1973) vol.22, no.3
> 
> # Evaluates the tail area of the standardised normal curve
> # from x to infinity if upper is true or
> # from minus infinity to x if upper is false
> 
> # ELF90 - compatible version by Alan Miller
> # Latest revision - 29 November 2001
> pure elemental function alnorm(x, upper) result(fn_val)
> real(dp), intent(in) :: x
> real(dp) :: fn_val
> # local variables
> real(dp), parameter :: zero = 0.0_dp, one = 1.0_dp, half = 0.5_dp, con = 1.28_dp
> real(dp) :: z, y
> # machine dependent constants
> real(dp), parameter :: ltone = 7.0_dp, utzero = 18.66_dp
> real(dp), parameter :: p = 0.398942280444_dp, q = 0.39990348504_dp, r = 0.398942280385_dp, a1 = 5.75885480458_dp, a2 = 2.62433121679_dp, a3 = 5.92885724438_dp, b1 = - 29.8213557807_dp, b2 = 48.6959930692_dp, c1 = - 3.8052e - 8_dp, c2 = 3.98064794e - 4_dp, c3 = - 0.151679116635_dp, c4 = 4.8385912808_dp, c5 = 0.742380924027_dp, c6 = 3.99019417011_dp, d1 = 1.00000615302_dp, d2 = 1.98615381364_dp, d3 = 5.29330324926_dp, d4 = - 15.1508972451_dp, d5 = 30.789933034_dp
> up = upper
> z = x
> if (z < zero) 
> up = ! up
> z = - z
> end
> if (z < = ltone || (up && z < = utzero)) end
> y = half * z * z
> if (z > con) 
> fn_val = r * exp( - y ) / (z + c1 + d1 / (z + c2 + d2 / (z + c3 + d3 / (z + c4 + d4 / (z + c5 + d5 / (z + c6))))))
> else
> fn_val = half - z * (p - q * y / (y + a1 + b1 / (y + a2 + b2 / (y + a3))))
> end
> else
> fn_val = zero
> end
> if ( ! up) fn_val = one - fn_val end
> end
> #
> pure elemental function cumnorm(x) result(fn_val)
> # adapted from alnorm with upper = false
> real(dp), intent(in) :: x
> real(dp) :: fn_val
> # local variables
> real(dp), parameter :: zero = 0.0_dp, one = 1.0_dp, half = 0.5_dp, con = 1.28_dp
> real(dp) :: z, y
> # machine dependent constants
> real(dp), parameter :: ltone = 7.0_dp, utzero = 18.66_dp
> real(dp), parameter :: p = 0.398942280444_dp, q = 0.39990348504_dp, r = 0.398942280385_dp, a1 = 5.75885480458_dp, a2 = 2.62433121679_dp, a3 = 5.92885724438_dp, b1 = - 29.8213557807_dp, b2 = 48.6959930692_dp, c1 = - 3.8052e - 8_dp, c2 = 3.98064794e - 4_dp, c3 = - 0.151679116635_dp, c4 = 4.8385912808_dp, c5 = 0.742380924027_dp, c6 = 3.99019417011_dp, d1 = 1.00000615302_dp, d2 = 1.98615381364_dp, d3 = 5.29330324926_dp, d4 = - 15.1508972451_dp, d5 = 30.789933034_dp
> z = x
> if (z < zero) 
> up = true
> z = - z
> else
> up = false
> end
> if (z < = ltone || (up && z < = utzero)) end
> y = half * z * z
> if (z > con) 
> fn_val = r * exp( - y ) / (z + c1 + d1 / (z + c2 + d2 / (z + c3 + d3 / (z + c4 + d4 / (z + c5 + d5 / (z + c6))))))
> else
> fn_val = half - z * (p - q * y / (y + a1 + b1 / (y + a2 + b2 / (y + a3))))
> end
> else
> fn_val = zero
> end
> if ( ! up) fn_val = one - fn_val end
> end
> #
> end
> 
> ```

that requires further editing, especially the declarations. Here is the

> **Julia translation script**
>
> ```julia
> #=
> 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 = ARGS[1]
> 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)
> 
> ```

---

<div class="post-metadata">

**Author:** ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)\
**Post date:** [November 19, 2021, 4:28am UTC](https://discourse.julialang.org/t/fix-script-that-translates-fortran-to-julia/71748/4 "2021-11-19T04:28:40Z")

</div>

I think the `if` statements are wrong in the pseudo-code.

---

<div class="post-metadata">

**Author:** ![Bardo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bardo/32/21601_2.png) [@Bardo](https://discourse.julialang.org/u/Bardo)\
**Post date:** [November 19, 2021, 5:47am UTC](https://discourse.julialang.org/t/fix-script-that-translates-fortran-to-julia/71748/5 "2021-11-19T05:47:17Z")

</div>

See also the online version of their [F77 → f90 converter](https://fortran.uk/plusfortonline.php).

Know other tools to restructure dusty GOTO into modern code?
