プログラムを生成するプログラム、という響きに何か憧れを感じています。
ということで、Fortran90のコードをJulia 1.1のコードに自動変換するコードを書いてみました。
結果的に、Juliaでの文字列の扱いを勉強できました。
Fortran90について
古い言語ですが、スーパーコンピュータなどの大規模数値計算では現役バリバリで使われています。
Fortran90の全ての言語仕様に対応することは難しいので、ふんわりと書き換えて、残った部分は手作業で直すような運用が良いかなと思います。
対応した文法
- Doループ
- subroutine
- function
- write文
- call文
- integerとreal(8)
-
!
によるコメントアウト
とりあえずよく出てきそうなものを重点的に変換できるようにしました。
現時点で未対応
- If文
- 配列
- module文
- complexやcharacterなど
- 再帰的なfunctionなど
1-4までは、時間のあるときに対応できたらと思います。5はFortran90の仕様なのか覚えていません。コードの中にそんなになければ、1-4まで対応していれば手作業でぱぱっとできるかと思います。なお、continue文などがあるFORTRAN77には対応する予定はありません。FORTRANのコードはまずfortranのコードに変えるのが良いでしょう。
Juliaについて
Juliaのバージョンは1.1を使いました。
有用だった関数
-
readlines
:ファイル名を入れると配列として読み込んでくれます -
countlines
:ファイルに含まれる行の数を数えます。 -
occursin("program",data_i)
:data_i
という文字列の中にprogram
が含まれていればtrueを返します。 -
split(data_i)
:よしなに文字列data_i
を区切ってくれます -
split(ut,",")
:ut
を,
で区切ってくれます。二個目の変数に好きな文字列を入れればそれで区切ってくれます。 -
filter!(x -> x != nstring,u)
: 文字列が格納された配列u
から、文字列nstring
を削除した配列を作ります。 -
replace(data_i, '*'*'*' => '^')
: 文字列data_i
に含まれている**
を^
に置き換えます。アスタリスク*
は`*`
で指定できました。 -
join(u," ")
:文字列を要素とする配列の中身をでつなげて一つの文字列にします。空白の代わりに任意の文字列を使うことが可能です。
-
findfirst("""(""",ut)
: 文字列ut
の中で(が何文字目にあるかを返します。 -
"\t"^(numtab)
:numtab
回、\t
(つまりタブ)を繰り返します。endの数やfunctionやsubroutineの数を数えて、コードにインデントがつくようにしています。 -
lowercase(data)
:文字列data
を全て小文字に変換します。Fortran90では大文字小文字を区別しませんが、コードの書き方としてはどちらも使う場合がありますので、ここは統一してからJuliaコードに変換しました。
変換したFortran90コード
最初のテストなので、比較的簡単なコードにしました。無駄にスペースや大文字などをいれています。
!!!!!!!!!!!!!!
!コメントをつけてみよう
!どんな関数だろうか
!!!!!!!!!!!!!!!!
program main
implicit none
real(8)::a,b
a = 10d0
b = 1d-5
call test()
call test2(a, b)
call test3(a , b)!これはtest3です。
end
subroutine Test()
implicit none
real(8)::a,b
a = 10
return
end
subroutine test2(a,b)
implicit none
real(8)::a,b
b = a*10
return
end
subroutine test3(a , b)
implicit none
real(8)::a,b
integer::i
b = a*10
do i = 1,10
call test2(a ,b)
write(*,*) i
end do
do i = 1,10,2
write(*,*) i**2,a , b, 10*b
end do
return
end
function testfunc (a,b )
implicit none
real(8)::a,b,testfunc
a = 10
return
end
gfortranでコンパイルして実行すると、出力結果は、
1
2
3
4
5
6
7
8
9
10
1 10.000000000000000 100.00000000000000 1000.0000000000000
9 10.000000000000000 100.00000000000000 1000.0000000000000
25 10.000000000000000 100.00000000000000 1000.0000000000000
49 10.000000000000000 100.00000000000000 1000.0000000000000
81 10.000000000000000 100.00000000000000 1000.0000000000000
のようになります。
Fortranコードを変換するJuliaコード
Fortranコードを文字列として読み込んで、ひたすら置き換えてみました。
function loadfile(filename)
data = readlines(filename)
numofdata = countlines(filename)
juliacode = []
numtab = 0
for i=1:numofdata
code,addtab= f2jl(data[i])
#println(code)
if addtab < 0
numtab += addtab
tabs = "\t"^(numtab)
code = tabs*code
else
tabs = "\t"^(numtab)
code = tabs*code
numtab += addtab
end
push!(juliacode,code)
println(code)
end
push!(juliacode,"main()")
println("main()")
return
end
function f2jl(data)
data_i = lowercase(data)
data_i = replace(data_i, '!' => '#')
addtab = 0
if occursin("program",data_i)
code = "function main()"
addtab = 1
elseif occursin("subroutine",data_i)
nstring = "subroutine"
u = split(data_i)
ut = join(filter!(x -> x != nstring,u)," ")
code = "function "*ut
addtab = 1
elseif occursin("function",data_i)
nstring = "function"
u = split(data_i)
ut = join(filter!(x -> x != nstring,u)," ")
ints = findfirst("""(""",ut)
inte = findfirst(""")""",ut)
functionname = strip(ut[1:ints[1]-1])
code = "function calc_"*functionname*ut[ints[1]:inte[1]-1]*","*functionname*")"
addtab = 1
elseif occursin("call",data_i)
nstring = "call"
u = split(data_i)
ut = join(filter!(x -> x != nstring,u)," ")
code = ut
elseif occursin("end",data_i)
code = "end"
addtab = -1
elseif occursin("do",data_i)
nstring = "do"
u = split(data_i)
ut = join(filter!(x -> x != nstring,u)," ")
ut = split(ut,",")
len = length(ut)
if len == 2
code = "for "*ut[1]*":"*ut[2]
elseif len == 3
code = "for "*ut[1]*":"*ut[3]*":"*ut[2]
else
println("error!")
println(ut)
stop()
end
addtab = 1
else
code = f2jl_detail(data_i)
end
return code,addtab
end
function f2jl_detail(data_i)
data_i = replace(data_i, '*'*'*' => '^')
data_i = replace(data_i, 'd'*'-' => 'e'*'-')
if occursin("implicit",data_i)
return ""
elseif occursin("real",data_i) && occursin("::",data_i)
return ""
elseif occursin("integer",data_i)
return ""
end
if occursin("write",data_i)
nstring = "write"
u = split(data_i)
ut = join(filter!(x -> x != nstring,u)," ")
ints = findfirst("""(""",ut)
inte = findfirst(""")""",ut)
ut = ut[inte[1]+1:end]
ut = replace(ut, "," => """, " " ,""")
code = "println"*"""("""*ut*""")"""
return code
end
if occursin("=",data_i)
for i=0:320
data_i = replace(data_i, 'd'*string(i) => 'e'*string(i))
end
end
u = split(data_i)
ut = join(u," ")
code = ut
return code
end
filename = "test.f90"
loadfile(filename)
このコードを実行すると標準出力にJuliaコードが出力されます。
出てきたコードは、
##############
#コメントをつけてみよう
#どんな関数だろうか
################
function main()
a = 10e0
b = 1e-5
test()
test2(a, b)
test3(a , b) #これはtest3です。
end
function test()
a = 10
return
end
function test2(a,b)
b = a*10
return
end
function test3(a , b)
b = a*10
for i = 1:10
test2(a ,b)
println( i)
end
for i = 1:2:10
println( i^2, " " ,a , " " , b, " " , 10*b)
end
return
end
function calc_testfunc(a,b ,testfunc)
a = 10
return
end
main()
となります。
これを実行すると、
1
2
3
4
5
6
7
8
9
10
1 10.0 100.0 1000.0
9 10.0 100.0 1000.0
25 10.0 100.0 1000.0
49 10.0 100.0 1000.0
81 10.0 100.0 1000.0
となります。Fortran90のコードと同じ結果ですね。
未対応な文法がある場合
なお、まだ対応していないFortran90の文法に遭遇した場合、そのまま書き出すことになるので、JuliaコードにいきなりFortran90文法が出現するコードができます。とりあえず出てきたコードを見てちょくちょく直せばいいかなと思います。