Thank you for the code alternative.
I like many of the changes you did: The non-quadratic tokenizer, of course, as well as the use of a stack of the minimum size needed, and the is_number
check.
I am less fond of the allocation of a temporary array to the possible maximum number of tokens, though, even if the data in the unused elements is not allocated. As I see it, the function you wrote splits naturally in a token counter and in a token reader. Once the number of tokens is known, it is easy to fetch them from the input expression with the index function.
Therefore, I would rather replace the tokenize function with something like the following
!> @brief
!> Count tokens in string.
pure integer function countTokens(expr,delimiter) result(nTokens)
character(*), intent(in) :: expr
character(*), intent(in) :: delimiter
!
integer :: low, high
low = 1; nTokens = 0
do while (low <= len(expr))
do while (expr(low : low) == delimiter)
low = low + 1
if (low > len(expr)) exit
end do
if (low > len(expr)) exit
high = low
if (high < len(expr)) then
do while (expr(high + 1 : high + 1) /= delimiter)
high = high + 1
if (high == len(expr)) exit
end do
end if
nTokens = nTokens + 1
low = high + 2
end do
end function countTokens
!> @brief
!> Split string by whitespace.
pure function tokenize(expr) result(res)
character(*), intent(in) :: expr
type(Token_t), allocatable :: res(:)
!
character(len=1), parameter :: delimiter = ' '
integer , parameter :: TOKEN_MAX_LEN = 50
!
character(len=TOKEN_MAX_LEN):: sBuf
integer :: iToken, nTokens, low
nTokens = countTokens(expr,delimiter)
allocate(res(nTokens))
low=1
do iToken = 1, nTokens
read(expr(low:),*) sBuf
res(iToken)%str = trim(adjustl(sBuf))
if(iToken == nTokens)exit
low = low + index(expr(low:),res(iToken)%str) - 1
low = low + index(expr(low:)," ")
enddo
end function
I am on the fence regarding the select case
statement. I originally used this same algorithm, but I did not like (and still do not like) repeating the boilerplate code A = stack%pop(); B = stack%pop()
over and over. The only possible advantage I can see is if the compiler implements a binary search across the listed cases (?), which would be best for a large number of operators, of course. If, however, it goes linearly through the cases, then the gain over a list of ifs is just of about a factor of two.
I understand also the appeal of operators with arbitrary arity. I think a possible approach to avoid too much boilerplate would be to separately specify the kind (fixed number, number specified at run time, or to be determined from the stack) and, if applicable, the value of this operator attribute, and on this basis popping as many operands as needed.