How to read a mixed text file and extract numbers only - fortran

I need to read the output of one of my simulators and store the values. the file name is forces.dat and contains a similar thing as the following:
# Forces
# CofR : (4.750000e-01 3.500000e-02 2.000000e-02)
# Time forces(pressure viscous porous) moment(pressure viscous porous)
2.633022e-02 ((6.268858e-02 -1.468850e+01 1.542745e-20) (1.000906e-03 8.405854e-06 -5.657665e-17) (0.000000e+00 0.000000e+00 0.000000e+00)) ((-8.779466e-18 8.442993e-19 -3.225599e-03) (-2.082489e-18 4.435609e-18 -1.572485e-03) (0.000000e+00 0.000000e+00 0.000000e+00))
8.095238e-02 ((1.781333e-01 -1.468455e+01 -3.545427e-19) (2.362118e-03 2.014609e-05 1.691584e-16) (0.000000e+00 0.000000e+00 0.000000e+00)) ((-3.344781e-18 -5.448339e-19 2.227502e-02) (5.092628e-18 -3.538718e-18 -1.203074e-03) (0.000000e+00 0.000000e+00 0.000000e+00))
1.600000e-01 ((3.204471e-01 -1.467482e+01 -4.599174e-18) (6.936764e-03 1.303800e-04 4.836650e-17) (0.000000e+00 0.000000e+00 0.000000e+00)) ((-1.123589e-17 -4.344967e-19 5.591623e-02) (1.532415e-18 -1.345592e-18 -9.550750e-04) (0.000000e+00 0.000000e+00 0.000000e+00))
I want to know how should I write a Fortran subroutine to ignore the first 3 lines and then read the number of next lines and then the values of each line.

You can use this snippet which will keep a track of line numbers. Based on your requirement and nature of file, you can get the values of respective lines and can do the required.
string CurrentLine;
int LastLineNumber;
void NextLine()
{
// using will make sure the file is closed
using(System.IO.StreamReader file = new System.IO.StreamReader ("c:\\forces.dat"))
{
// Skip lines
for (int i=0;i<LastLineNumber;++i)
file.ReadLine();
// Store your line
CurrentLine = file.ReadLine();
LastLineNumber++;
}
}
In the above code, inside for loop you can put in your logic of file processing based on the lines you want to read.

Although I think it is easier to preprocess the file by some command-line tool (e.g. sed -e 's/(/ /g' -e 's/)/ /g' input.dat), we can also use Fortran directly by reading each line into a long character string and removing all the unnecessary parentheses:
program main
implicit none
integer, parameter :: mxline = 5000 !! choose appropriately
integer i, ios, finp, nl
character(500) str
real, save :: time( mxline )
real, dimension( 3, mxline ), save :: &
frc_pres, frc_visc, frc_poro, &
mom_pres, mom_visc, mom_poro
finp = 10
open( finp, file="input.dat", status="old" )
nl = 0
do
read( finp, "(a)", iostat=ios ) str
if ( ios /= 0 ) exit
str = trim( adjustL( str ) )
!! Skip comment or blank lines.
if ( str(1:1) == "#" .or. str == "" ) cycle
!! Replace parentheses with space.
do i = 1, len_trim( str )
if ( str(i:i) == "(" .or. str(i:i) == ")" ) str(i:i) = " "
enddo
!! Read data from the string.
nl = nl + 1
read( str, * ) time( nl ), &
frc_pres( :, nl ), frc_visc( :, nl ), frc_poro( :, nl ), &
mom_pres( :, nl ), mom_visc( :, nl ), mom_poro( :, nl )
enddo
close( finp )
!! Check.
do i = 1, nl
print *
print *, "time = ", time( i )
print *, "frc_pres = ", frc_pres( :, i )
print *, "frc_visc = ", frc_visc( :, i )
print *, "frc_poro = ", frc_poro( :, i )
print *, "mom_pres = ", mom_pres( :, i )
print *, "mom_visc = ", mom_visc( :, i )
print *, "mom_poro = ", mom_poro( :, i )
enddo
end program
If the data values can become very large (say, 1.0e100), please consider using double-precision reals so as not to loose necessary precision.

Related

How to properly format exponential scientific notation for a double precision value? [duplicate]

This question already has answers here:
Fortran assignment on declaration and SAVE attribute gotcha
(2 answers)
Does Fortran preserve the value of internal variables through function and subroutine calls?
(3 answers)
Closed last year.
I am overriding the concatenation operator to allow real values to be concatenated with strings.
function concatenateRealSigFigsWithString( floatIn, str ) result(cat)
! Arguments
character( len=* ), intent(in) :: str
real(8), intent(in) :: floatIn(2)
! Returns
character( len=: ), allocatable :: cat
! Variables
logical :: remove_decimal = .False.
real(8) :: float
character( len=16 ) :: float_as_str, fmt
integer :: sigfig, i
float = floatIn(1)
sigfig = int(floatIn(2))
do i = sigfig, 1, -1
if (float < 10.0_8**i .and. float > 10.0_8**(i-1) ) then
fmt = '(F' // sigfig + 1 // '.' // sigfig - i // ')'
if (i == sigfig) remove_decimal = .True.
end if
end do
if ( float < 1 ) then
fmt = '(F' // sigfig + 2 // '.' // sigfig // ')'
end if
if (float < 0.1 ) then
fmt = '(ES' // sigfig + 5 // '.' // sigfig - 1 // ')'
end if
if ( float > 10.0_8**sigfig ) then
fmt = '(ES' // sigfig+6 // '.' // sigfig-1 // ')'
end if
write(float_as_str,fmt) float
if (remove_decimal) float_as_str = float_as_str(1:len_trim(float_as_str)-1)
cat = trim(float_as_str) // str
! cat = fmt
end function concatenateRealSigFigsWithString
The real value is a actually a list. The first element is the number and the second is the significant figures. Values between 0 and 10^(sigfig) are given in decimal form. Otherwise, the value is given in scientific form. The concatenation works for all values except those greater than 10^(sigfig). So the focus of the issue here is in the line fmt = '(ES' // sigfig+6 // '.' // sigfig-1 // ')'. For example, a value of 300,000 that should be written to 3 significant figures is formatted as 3.00E+0.
I can't figure out why the exponent is only showing one digit instead of 2, since it should be 3.00E+05.
I have tried specifying the width of exponent like fmt = '(ES' // sigfig+6 // '.' // sigfig-1 // 'E2)' but that just gives me 3.00E+00. I also tried expanding the width in case I did my math wrong and the exponent was getting cut off, but all that did was increase leading white space.
Any idea why the exponent is not showing the true value. Here is the code I used to test it.
program test
interface operator(//)
procedure :: concatenateRealSigFigsWithString, concatenateStringWithInteger
end interface
real(8) :: sigfig = 3
print *, [2.337e-7_8, sigfig] // " slugs"
print *, [2.337e-6_8, sigfig] // " slugs"
print *, [2.337e-5_8, sigfig] // " slugs"
print *, [2.337e-4_8, sigfig] // " slugs"
print *, [2.337e-3_8, sigfig] // " slugs"
print *, [2.337e-2_8, sigfig] // " slugs"
print *, [2.337e-1_8, sigfig] // " slugs"
print *, [2.337e0_8, sigfig] // " slugs"
print *, [2.337e01_8, sigfig] // " slugs"
print *, [2.337e02_8, sigfig] // " slugs"
print *, [2.337e03_8, sigfig] // " slugs"
print *, [2.337e4_8, sigfig] // " slugs"
print *, [2.337e5_8, sigfig] // " slugs"
print *, [2.337e6_8, sigfig] // " slugs"
print *, [2.337e7_8, sigfig] // " slugs"
print *, [2.337e8_8, sigfig] // " slugs"
print *, [2.337e9_8, sigfig] // " slugs"
print *, [2.337e10_8, sigfig] // " slugs"
contains
function concatenateStringWithInteger(str,int) result(cat)
! Arguments
character( len=* ), intent(in) :: str
integer, intent(in) :: int
! Returns
character( len=: ), allocatable :: cat
! Variables
character( len=9 ) :: int_as_str
write(int_as_str,'(I0)') int
cat = str // trim(int_as_str)
end function concatenateStringWithInteger
function concatenateRealSigFigsWithString( floatIn, str ) result(cat)
! Arguments
character( len=* ), intent(in) :: str
real(8), intent(in) :: floatIn(2)
! Returns
character( len=: ), allocatable :: cat
! Variables
logical :: remove_decimal = .False.
real(8) :: float
character( len=16 ) :: float_as_str, fmt
integer :: sigfig, i
float = floatIn(1)
sigfig = int(floatIn(2))
do i = sigfig, 1, -1
if (float < 10.0_8**i .and. float > 10.0_8**(i-1) ) then
fmt = '(F' // sigfig + 1 // '.' // sigfig - i // ')'
if (i == sigfig) remove_decimal = .True.
end if
end do
if ( float < 1 ) then
fmt = '(F' // sigfig + 2 // '.' // sigfig // ')'
end if
if (float < 0.1 ) then
fmt = '(ES' // sigfig + 5 // '.' // sigfig - 1 // ')'
end if
if ( float > 10.0_8**sigfig ) then
fmt = '(ES' // sigfig+6 // '.' // sigfig-1 // ')'
end if
write(float_as_str,fmt) float
if (remove_decimal) float_as_str = float_as_str(1:len_trim(float_as_str)-1)
cat = trim(float_as_str) // str
! cat = fmt
end function concatenateRealSigFigsWithString
end program test

String trimming for output format

This is my code:
Program String_Triming
Implicit none
Open(15, File = 'Output.txt')
Write(15,'(A,1x,"j",1x,A)') Ispis(20.45),Ispis(20.45)
Write(15,'(A,1x,"j",1x,A)') Ispis(-20.45),Ispis(-20.45)
Close(15)
Contains
Function Ispis ( Deg ) result ( Str )
Real,intent(in)::Deg
Character(len=16):: Str
If ( Deg > 0 ) then
Write(Str,'(F0.3)') 1000.0 + Deg
Str = Str(2:)
Else
Write(Str,'(F8.3)') 1000.0 + abs(Deg)
Write(Str,'("-",A)') Str(3:)
End If
End Function Ispis
End program String_Triming
The content of Output.txt file is:
020.450 j 020.450
-20.450 j -20.450
The result I want to get from this code is:
020.450 j 020.450
-20.450 j -20.450
How do I get that result? Is there way to trim the length of Str to Len=8 which is the length of 020.450?
It's not quite clear what you want. If all you want is to remove the spaces from the output file, why not just run it through sed instead of writing a Fortran Program:
$ cat Output.txt
020.450 j 020.450
-20.450 j -20.450
$ sed -r 's/ +/ /g' Output.txt
020.450 j 020.450
-20.450 j -20.450
If you want to produce output like this in the first place, you could 'overwrite' the first three characters of str with an integer format. Something like this:
function Ispis(Deg) result(Str)
real, intent(in) :: Deg
character(len=7) :: Str
write(Str, '(F7.3)') Deg
if ( Deg < 0 ) then
write(Str(:3), '(I3.2)') int(Deg)
else
write(Str(:3), '(I3.3)') int(Deg)
end if
end function Ispis
note: the length of 020.450 is 7, not 8.
This is the solution for getting wanted result:
Program Main
Implicit none
Open(15,File='Output.txt')
Write(15,'(1x,a,1x,"j",1x,a,1x,"Juhu!")') Writing_01(67.45),Writing_01(-4.04)
Write(15,'(1x,a,1x,"j",1x,a,1x,"Juhu!")') Writing_02(67.45),Writing_02(-4.04)
Close(15)
Contains
Function Writing_01 ( Deg ) Result ( Str )
Real,intent(in) :: Deg
Character(:),allocatable :: Str
Character(len = 15 ) :: Str_temp
If ( int( Deg ) > 0 ) then
Write(Str_temp , '(F0.2)' ) 100000.0 + Deg
Str_temp = Str_temp(2:)
Else
Write(Str_temp, '(F0.2)' ) 100000.0 + abs(Deg)
Str_temp = "-"//Str_temp(3:)
Endif
Str = trim ( adjustl ( Str_temp ))
End Function Writing_01
Function Writing_02 ( Deg ) Result ( Str_temp )
Real,intent(in) :: Deg
Character(:),allocatable :: Str_temp
Character(len=1561) :: Form_02 , Res
If (int( Deg ) > 0 ) then
Form_02 = '(i5.5,f0.2)' ! allow a total of 4 leading zeros.
Else
Form_02 = '(i5.4,f0.2)' ! "-" sign takes up one space, so 3 leading zeros remain.
Endif
Write(Res , Form_02 ) int( Deg ), abs( Deg - int( Deg ) )
Str_temp = trim ( adjustl ( Res ))
End Function Writing_02
End program Main

data entrance error Fortran

I'm learning how to programming with fortran90 and i need receive data from a txt file by the command prompt (something like that:
program.exe"<"data.txt).
at the Input txt file I'll always have a single line with at least 6 numbers till infinity.
if the data was wrote line by line it runs fine but as single line I'm receiving the error: "traceback:not available,compile with - ftrace=frame or - ftrace=full fortran runtime error:end file"
*note: i'm using Force fortran 2.0
here is example of data:
0 1 0.001 5 3 1 0 -9 3
edit: just clarifying: the code is working fine itself except for the read statement, which is a simple "read*,". I want know how To read a entire line from a txt once the entrance will be made by the promt command with stream direction.
( you can see more about that here: https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/redirection.mspx?mfr=true).
there is no need to read the code, i've posted it just for knowledge.
I'm sorry about the whole inconvenience.
here is the code so far:
program bissecao
implicit none
integer::cont,int,e,k,intc,t1,t2,t3
doubleprecision::ii,is,pre,prec,erro,somaa,somab,xn
doubleprecision,dimension(:),allocatable::co
t1=0
t2=0
t3=0
! print*,"insira um limite inf da funcao"
read*,ii
!print*,"insira o limite superior da func"
read*,is
! print*,"insira a precisÆo admissivel"
read*,pre
if (erro<=0) then !elimina criterio de parada negativo ou zero
Print*,"erro"
go to 100
end if
!print*,"insira a qtd iteracoes admissiveis"
read*,int
!print*,"insira o grau da f(x)"
read*,e
if (e<=0) then ! elimina expoente negativo
e=(e**2)**(0.5)
end if
allocate(co(e+1))
!print*, "insira os coeficientes na ordem:&
! &c1x^n+...+(cn-1)x^1+cnx^0"
read(*,*)(co(k),k=e+1,1,-1)
somab=2*pre
intc=0
do while (intc<int.and.(somab**2)**0.5>pre.and.((is-ii)**2)**0.5>pre)
somab=0
somaa=0
xn =(ii+is)/2
do k=1,e+1,1
if (ii /=0) then
somaa=ii**(k-1)*co(k)+somaa
else
somaa=co(1)
end if
! print*,"somaa",k,"=",somaa
end do
do k=1,(e+1),1
if (xn/=0) then
somab=xn**(k-1)*co(k)+somab
else
somab=co(1)
end if
!print*,"somab",k,"=",somab
end do
if ((somaa*somab)<0) then
is=xn
else if((somaa*somab)>0)then
ii=xn
else if ((somaa*somab)==0) then
xn=(ii+is)/2
go to 100
end if
intc =intc+1
prec=is-ii
if ((((is-ii)**2)**.5)< pre) then
t3=1
end if
if (((somab**2)**.5)< pre) then
t2=1.
end if
if (intc>=int) then
t1=1
end if
end do
somab=0
xn=(ii+is)/2
do k=1,(e+1),1
if (xn/=0) then
somab=xn**(k-1)*co(k)+somab
else
somab=co(1)
end if
end do
100 write(*,'(A,F20.15,A,F20.15,A,A,F20.15,A,F20.15,A,I2)'),"I:[",ii,",",is,"]","raiz:",xn,"Fraiz:",somab,"Iteracoes:",intc
end program !----------------------------------------------------------------------------
In your program, you are using the "list-directed input" (i.e., read *, or read(*,*))
read *, ii
read *, is
read *, pre
read *, int
read *, e
read *, ( co( k ), k = e+1, 1, -1 )
which means that the program goes to the next line of the data file after each read statement (by neglecting any remaining data in the same line). So, the program works if the data file (say "multi.dat") consists of separate lines (as suggested by OP):
0
1
0.001
5
3
1 0 -9 3
But now you are trying to read an input file containing only a single line (say "single.dat")
0 1 0.001 5 3 1 0 -9 3
In this case, we need to read all the values with a single read statement (if list-directed input is to be used).
A subtle point here is that the range of array co depends on e, which also needs to be read by the same read statement. A workaround might be to just pre-allocate co with a sufficiently large number of elements (say 100) and read the data in a single line, e.g.,
integer :: k
allocate( co( 100 ) )
read *, ii, is, pre, int, e, ( co( k ), k = e+1, 1, -1 )
For completeness, here is a test program where you can choose method = 1 or 2 to read "multi.dat" or "single.dat".
program main
implicit none
integer :: int, e, k, method
double precision :: ii, is, pre
double precision, allocatable :: co(:)
allocate( co( 1000 ) )
method = 1 !! 1:multi-line-data, 2:single-line-data
if ( method == 1 ) then
call system( "cat multi.dat" )
read*, ii
read*, is
read*, pre
read*, int
read*, e
read*, ( co( k ), k = e+1, 1, -1 )
else
call system( "cat single.dat" )
read*, ii, is, pre, int, e, ( co( k ), k = e+1, 1, -1 )
endif
print *, "Input data obtained:"
print *, "ii = ", ii
print *, "is = ", is
print *, "pre = ", pre
print *, "int = ", int
print *, "e = ", e
do k = 1, e+1
print *, "co(", k, ") = ", co( k )
enddo
end program
You can pass the input file from standard input as
./a.out < multi.dat (for method=1)
./a.out < single.dat (for method=2)
Please note that "multi.dat" can also be read directly by using "<".

Fortran read mixed text and numbers

I am using Fortran 90 to read a file that contains data in the following format
number# 125 var1= 2 var2= 1 var3: 4
.
.
.
.
number# 234 var1= 3 var2= 5 var3: 1
I tried the following command and works fine
read (2,*) tempstr , my_param(1), tempstr , my_param(2), tempstr , my_param(3)
Problem is when the numbers become larger and there is no space between string and number, i.e. the data looks as following:
number# 125 var1= 2 var2=124 var3: 4
I tried
read (2,512) my_param(1), my_param(2), my_param(3)
512 format('number#', i, 'var1=', i, 'var2=', i, 'var3:', i)
It reads all number as zero
I can't switch to some other language. The data set is huge, so I can't pre-process it. Also, the delimiters are not the same every time.
Can someone please help with the problem?
Thanks in advance
First up, 720 thousand lines is not too much for pre-processing. Tools like sed and awk work mostly on a line-by-line basis, so they scale really well.
What I have actually done was to convert the data in such a way that I could use namelists:
$ cat preprocess.sed
# Add commas between values
# Space followed by letter -> insert comma
s/ \([[:alpha:]]\)/ , \1/g
# "number" is a key word in Fortran, so replace it with num
s/number/num/g
# Replace all possible data delimitors with the equals character
s/[#:]/=/g
# add the '&mydata' namelist descriptor to the beginning
s/^/\&mydata /1
# add the namelist closing "/" character to the end of the line:
s,$,/,1
$ sed -f preprocess.sed < data.dat > data.nml
Check that the data was correctly preprocessed:
$ tail -3 data.dat
number#1997 var1=114 var2=130 var3:127
number#1998 var1=164 var2=192 var3: 86
number#1999 var1=101 var2= 48 var3:120
$ tail -3 data.nml
&mydata num=1997 , var1=114 , var2=130 , var3=127/
&mydata num=1998 , var1=164 , var2=192 , var3= 86/
&mydata num=1999 , var1=101 , var2= 48 , var3=120/
Then you can read it with this fortran program:
program read_mixed
implicit none
integer :: num, var1, var2, var3
integer :: io_stat
namelist /mydata/ num, var1, var2, var3
open(unit=100, file='data.nml', status='old', action='read')
do
read(100, nml=mydata, iostat=io_stat)
if (io_stat /= 0) exit
print *, num, var1, var2, var3
end do
close(100)
end program read_mixed
While I still stand with my original answer, particularly because the input data is already so close to what a namelist file would look like, let's assume that you really can't make any preprocessing of the data beforehand.
The next best thing is to read in the whole line into a character(len=<enough>) variable, then extract the values out of that with String Manipulation. Something like this:
program mixed2
implicit none
integer :: num, val1, val2, val3
character(len=50) :: line
integer :: io_stat
open(unit=100, file='data.dat', action='READ', status='OLD')
do
read(100, '(A)', iostat=io_stat) line
if (io_stat /= 0) exit
call get_values(line, num, val1, val2, val3)
print *, num, val1, val2, val3
end do
close(100)
contains
subroutine get_values(line, n, v1, v2, v3)
implicit none
character(len=*), intent(in) :: line
integer, intent(out) :: n, v1, v2, v3
integer :: idx
! Search for "number#"
idx = index(line, 'number#') + len('number#')
! Get the integer after that word
read(line(idx:idx+3), '(I4)') n
idx = index(line, 'var1') + len('var1=')
read(line(idx:idx+3), '(I4)') v1
idx = index(line, 'var2') + len('var3=')
read(line(idx:idx+3), '(I4)') v2
idx = index(line, 'var3') + len('var3:')
read(line(idx:idx+3), '(I4)') v3
end subroutine get_values
end program mixed2
Please note that I have not included any error/sanity checking. I'll leave that up to you.

Targeting a specific occurence in a string and replacing it

I have a string with multiple / in it and I am trying to convert this string in to LaTeX code. Basically (a)/(b) becomes \\dfrac{a}{b}.
The difficulty is that (a) and/or (b) could contain other /.
To respect the parentheses balancing, I would like to replace the / from left to right, and replacing them accordingly to what it around. I made a try but I don't know how target a specific / and replace. using position and length parameters seems to be very complicated.
function ToFrac (s)
while s:find ("/") ~= nil
do
-- Replace : \dfrac{}{}/() -> \dfrac{\dfrac...}{}
if ( s:find ( '\\dfrac%b{}%b{}/%b()' , j ) ~= nil )
then
x,y,num,den = s:find( '(\\dfrac%b{}%b{})/(%b())' )
den = den:gsub( '.(.+).' , '%1' )
s = s:gsub( '(\\dfrac%b{}%b{})/(%b())',
"\\dfrac{"..num.."}{"..den.."}" , 1 )
end
print ('### -- ', s)
-- Replace : ()/\dfrac{}{} -> \dfrac[}]{\dfrac...}
if ( s:find ( '(%b()/\\dfrac%b{}%b{}' ) ~= nil )
then
x,y,num,den = s:find( '((%b())/(\\dfrac%b{}%b{})' )
num = num:gsub( '.(.+).' , '%1' )
s = s:gsub( '((%b())/()\\dfrac%b{}%b{})',
"\\dfrac{"..num.."}{"..den.."}" , 1 )
end
print ('### -- ', s)
-- Replace : ()/() -> \dfrac{}{}
if ( s:find ( '%b()/%b()' , 1 ) ~= nil )
then
x,y,num,den = s:find( '(%b())/(%b())' )
num = num:gsub( '.(.+).' , '%1' )
den = den:gsub( '.(.+).' , '%1' )
s = s:gsub( '(%b())/(%b())',
"\\dfrac{"..num.."}{"..den.."}" , 1 )
Done = true
end
print ('### -- ', s)
end -- while
return (s)
end
s = "((a)/(b))/(c)"
print (s, ToFrac(s))
s = "(a)/((b)/(c))"
print (s, ToFrac(s))
s = "(a)/(b)/(c)/(d))"
print (s, ToFrac(s))
s = "((a)/(b))/((c)/(d))"
print (s, ToFrac(s))
Amended version of rpattiso's idea:
function to_frac(expr)
local t
return expr == '' and '' or (expr..'()'):gsub('(.-)(%b())',
function(prefix, subexpr)
local replace_with = ''
if not prefix:find'^%s*/%s*$' then
t, replace_with = {}, (not t and ''
or t[2] and '\\dfrac{'..t[1]..'}{'..t[2]..'}'
or '('..t[1]..')')..prefix
elseif t[2] then
t = {'\\dfrac{'..t[1]..'}{'..t[2]..'}'}
end
table.insert(t, to_frac(subexpr:sub(2,-2)))
return replace_with
end
)
end
print(to_frac' (a )/((b) / (c))') --> \dfrac{a }{\dfrac{b}{c}}
print(to_frac'((a)/((b)/(c)))/(e)') --> \dfrac{\dfrac{a}{\dfrac{b}{c}}}{e}
print(to_frac'(a)/(b)/(c)/(d)') --> \dfrac{\dfrac{\dfrac{a}{b}}{c}}{d}
The 'replace' argument of string.gsub can be a function.
Using that function, you can apply the substitution recursively to the numerator and denominator and build the result that way. string.sub can be used to remove the parentheses from the numerator and denominator.
function to_frac(expr)
return (expr:gsub('%s*(%b())%s*/%s*(%b())%s*',
function(num, denom)
return '\\dfrac{'..to_frac(num:sub(2,-2))..'}{'
..to_frac(denom:sub(2,-2))..'}'
end))
end
expr = ' (a )/((b) / (c))' -- \dfrac{a }{\dfrac{b}{c}}
print(to_frac(expr))
expr = '((a)/((b)/(c)))/(e)' -->\dfrac{\dfrac{a}{\dfrac{b}{c}}}{e}
print(to_frac(expr))
If you want to go beyond using parentheses for delimiting arguments and obey precedence rules, then look into LPeg.