recursive subroutine build_tree(formula,n,operand,tree,last_node,  &
  max_level,min_level,error)
  use gildas_def
  use sic_dependencies_interfaces
  use sic_interfaces, except_this=>build_tree
  use sic_expressions
  use sic_dictionaries
  use sic_types
  use gbl_message
  !---------------------------------------------------------------------
  ! @ private
  !   Formula interpretation.
  ! Transforms formula into its tree equivalent. Each node of the tree
  ! contains:
  !       - level of the node
  !       - code of operator
  !       - pointer to previous node
  !       - pointer to next node
  !       - number of associated operands
  !       - for each operand, a pointer to operand
  !  Recursive for e.g. evaluating ANY(A.EQ.1). The function ANY invokes
  ! its own build_tree to compute the array 'A.EQ.1'.
  !---------------------------------------------------------------------
  character(len=*),   intent(in)  :: formula                 ! The line to be analysed
  integer(kind=4),    intent(in)  :: n                       ! Length of line
  type(sic_descriptor_t)          :: operand(0:maxoper)      ! List of operands (output)
  integer(kind=4)                 :: tree(formula_length*2)  ! Tree structure (output)
  integer(kind=4),    intent(out) :: last_node               ! Pointer to last node in tree
  integer(kind=4),    intent(out) :: max_level               ! Maximum parenthesis level
  integer(kind=4),    intent(out) :: min_level               !
  logical,            intent(out) :: error                   ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='MTH'
  integer(kind=4) :: level(formula_length),coma(formula_length)
  integer(kind=4) :: missing_operand(maxpar)  ! Pointers to previous missing operands
  integer(kind=4) :: sleep_missing(maxpar)    ! Pointers to sleeping missing operands
  integer(kind=4) :: nmiss                    ! Number of active missing operands
  integer(kind=4) :: nsleep                   ! Number of sleeping missing operands
  integer(kind=4) :: iprev_node               ! Pointer to previous node
  integer(kind=4) :: lev                      ! Current level
  integer(kind=4) :: left_space
  integer(kind=4) :: preced                   ! Precedence level of operator
  integer(kind=4) :: ip                       ! Pointer to current character in line
  character(len=80) :: chain
  integer(kind=4) :: i,i_deb,i_fin,code,itree,i_operand
  integer(kind=4) :: n_operand(2),i_oper,next,prev_code,funcode,n_operand_actual
  integer(kind=4) :: ip0,i_beg
  ! Data
  data level/formula_length*0/, coma/formula_length*0/
  !
  if (n.gt.formula_length) then
    call sic_message(seve%e,rname,'Formula is too long')
    error = .true.
    return
  endif
  error = .false.
  funcode = code_nop
  !
  ! Get parenthesis levels.
  ! This routine is both inefficient and inelegant. Doing the corresponding
  ! work in FIND_OPERAND would save both CPU time and memory space.
  ! This is easy for level (just count parentheses, and add their number to
  ! current number), but not so simple for coma. I see no efficient way of doing
  ! it without one more pointer level. The best is probably to reserve the space
  ! for two operands in all cases where their number is unknown. The cost is
  ! at most one word per node, which is more than offset by suppressing LEVEL
  ! and COMA. In practice, this number is known for all operators, except
  ! MAX and MIN. Would fixing it to 2 be considered a limitation?
  !
  ! NO: Now fixed to 2 for some cases (optimized formulas)
  !
  call get_level(formula,n,level,coma,error)
  if (error) return
  !
  ! Initialise various pointers
  ip=1
  itree = 1
  last_node = 1
  i_operand = 1
  nmiss = 0
  nsleep = 0
  max_level = 0
  min_level = 32767
  lev = 0
  iprev_node = 0
  prev_code = code_nop
  code = code_nop              ! new
  n_operand(:) = 0
  n_operand_actual = 0
  do i = 1,maxoper
    operand(i)%status = empty_operand
  enddo
  missing_operand(:) = 0
  sleep_missing(:) = 0
  !
  ! Start the analysis
  do while (ip.le.n)
    ! Check for tree buffer space. Two operand nodes have length 7, so checking
    ! with 7 will do for all cases,
    left_space = maxtree-itree+1
    if (left_space.lt.7) then
      call sic_message(seve%e,rname,'Arithmetic expression is too complex')
      goto 99
      ! Check for operand buffer space
    elseif (i_operand.gt.maxoper) then
      call sic_message(seve%e,rname,'Arithmetic expression is too complex')
      goto 99
    endif
    ! Look for next operator in line
    call find_operator(formula(ip:n),n-ip+1,i_deb,i_fin,i_oper,code,preced,  &
    n_operand,next,error)
    if (error) then
      call sic_message(seve%e,rname,'Error in FIND_OPERATOR')
      goto 99
    endif
    !
    ! Avoid false positive operator names in variable name, file name, or
    ! expression
    if (funcode.eq.code_exist .or.  &
        funcode.eq.code_file  .or.  &
        funcode.eq.code_all   .or.  &  ! e.g. ALL(A.eq.1)
        funcode.eq.code_any   .or.  &
        funcode.eq.code_symbol) then
      ip0   = ip
      i_beg = i_deb
      do while (code.gt.code_not)
        ip = ip+next-1
        call find_operator(formula(ip:n),n-ip+1,i_deb,i_fin,i_oper,code,  &
        preced,n_operand,next,error)
      enddo
      ! Recompute offset from inital IP
      next = next + ip - ip0
      i_fin = i_fin + ip - ip0
      i_deb = i_beg
      ip = ip0
    endif
    !
    ! If we found a function operator, keep track of it. Some of them
    ! need a special processing when we will parse its operand
    if (code.eq.code_exist    .or.  &
        code.eq.code_typeof   .or.  &
        code.eq.code_rank     .or.  &
        code.eq.code_file     .or.  &
        code.eq.code_function .or.  &
        code.eq.code_len      .or.  &
        code.eq.code_len_trim .or.  &
        code.eq.code_all      .or.  &
        code.eq.code_any      .or.  &
        code.eq.code_isnan    .or.  &
        code.eq.code_symbol) then
      funcode = code
    endif
    !
    if (code.eq.code_coma) then
      !
      ! A coma has been encountered.
      if (prev_code.eq.code_nop) then
        call sic_message(seve%e,rname,'Invalid arithmetic formula '//formula)
        goto 99
      endif
      ! Read operand at address I_OPERAND.
      call read_operand(formula(ip+i_deb-1:ip+i_fin-1),i_fin-i_deb+1,  &
        operand(i_operand),error)
      if (error) then
        call sic_message(seve%e,rname,'Error in READ_OPERAND')
        goto 99
      endif
      ! Fill all pending missing operand requests with I_OPERAND
      do i= 1, nmiss
        tree(missing_operand(i)) = i_operand
      enddo
      i_operand = i_operand + 1
      ! Unstack top sleeping request.
      if (nsleep.gt.0) then
        missing_operand(1) = sleep_missing(nsleep)
        nsleep = nsleep - 1
        nmiss  = 1
      else
        nmiss = 0
      endif
      !
      ! One real operator found
    elseif (code.ne.code_nop) then
      !                  sic_quiet = quiet
      lev = 10*level(ip+i_oper-1) + preced
      if ( (code.eq.code_uminus.or.code.eq.code_uplus) .and.  &
            prev_code.eq.code_power) then
        lev = lev+1
      endif
      prev_code = code
      max_level = max(max_level,lev)
      min_level = min(min_level,lev)
      !
      tree(itree)   = lev
      tree(itree+1) = code
      tree(itree+2) = iprev_node
      ! This is a function call
      if (code.ge.min_code_func) then
        ! Check number of arguments
        n_operand_actual = 1+coma(ip+i_oper-1)
        if (n_operand_actual.lt.n_operand(1) .or. &
            n_operand_actual.gt.n_operand(2)) then
          if (code.ne.code_pyfunc) then
            chain = 'Invalid number of arguments in call to '//  &
                    formula(ip:ip+i_oper-1)
            call sic_message(seve%e,rname,chain)
            goto 99
          endif
        endif
        ! One missing operand request for each argument. Put the first one in
        ! active scope
        nmiss = nmiss + 1
        if (nmiss.gt.maxpar) then
          call sic_message(seve%e,rname,'Arithmetic expression is too complex')
          goto 99
        endif
        missing_operand(nmiss) = itree + 5
        ! Stack all others in sleeping scope.
        do i=n_operand_actual,2,-1
          nsleep = nsleep + 1
          if (nsleep.gt.maxpar) then
            call sic_message(seve%e,rname,'Arithmetic expression is too '//  &
            'complex')
            goto 99
          endif
          sleep_missing(nsleep) = itree + 4 + i
        enddo
        !
      elseif (code.eq.code_uplus .or. code.eq.code_uminus .or.  &
              code.eq.code_not) then  ! Unary + and -, .NOT.
        ! Queue a missing operand request
        nmiss = nmiss + 1
        if (nmiss.gt.maxpar) then
          call sic_message(seve%e,rname,'Arithmetic expression is too complex')
          goto 99
        endif
        missing_operand(nmiss) = itree + 5
        n_operand_actual = n_operand(1)  ! Probably does not make sense to "get" the
                                         ! actual number of arguments to these operators
        !
      else
        ! Arithmetic and logical binary operators
        call read_operand_byfunc(funcode,formula(ip+i_deb-1:ip+i_fin-1),  &
            i_fin-i_deb+1,operand(i_operand),error)
        if (error)  goto 99
        ! Fill all pending missing operand requests with I_OPERAND
        tree(itree+5) = i_operand
        do i= 1, nmiss
          tree(missing_operand(i)) = i_operand
        enddo
        i_operand = i_operand + 1
        ! Queue one missing operand request for second operand
        nmiss = 1
        missing_operand(nmiss) = itree + 6
        n_operand_actual = n_operand(1)  ! Can we know (and check?) the number of
                                         ! arguments in this case?
      endif
      ! Fill up the node
      iprev_node = itree
      tree(itree+3) = itree + 5 + n_operand_actual
      tree(itree+4) = n_operand_actual
      last_node = itree
      itree = itree + 5 + n_operand_actual
      !
    else  ! code.eq.code_nop
      ! End of line reached: process last operand
      call read_operand_byfunc(funcode,formula(ip+i_deb-1:ip+i_fin-1),  &
        i_fin-i_deb+1,operand(i_operand),error)
      if (error)  goto 99
      ! Fill all pending missing operand requests.
      do i= 1, nmiss
        tree(missing_operand(i)) = i_operand
      enddo
      if (nsleep.gt.0) then
        chain = 'Missing operand in formula '//formula(1:n)
        call sic_message(seve%w,rname,chain)
      else
        nmiss = 0
      endif
      ! No operator at all: create a dummy node with NOP as operator
      if (itree.eq.1) then
        tree(itree)   = 0
        tree(itree+1) = code_nop
        tree(itree+2) = iprev_node
        tree(itree+3) = 0
        tree(itree+4) = 1
        tree(itree+5) = i_operand
        last_node = itree
      else
        tree(iprev_node+3) = 0
      endif
    endif
    ! Update line pointer
    ip = ip + next - 1
  enddo
  if (nmiss.gt.0) then
    chain = 'Missing operand in formula '//formula(1:n)
    call sic_message(seve%w,rname,chain)
    error = .true.
  endif
  return
  !
  ! Error return
99 error = .true.
  !
  ! Free everything (in principle, no free status is present)
  do i=1,maxoper
    if (operand(i)%status.eq.free_operand) then
      call sic_message(seve%w,rname,'Free operand in BUILD_TREE')
      call free_vm (operand(i)%size,operand(i)%addr)
    elseif (operand(i)%status.eq.scratch_operand) then
      call free_vm (operand(i)%size,operand(i)%addr)
    endif
    operand(i)%status = empty_operand
  enddo
end subroutine build_tree
!
subroutine read_operand(chain,nch,descr,error)
  use gildas_def
  use sic_dependencies_interfaces
  use sic_interactions
  use sic_interfaces, except_this=>read_operand
  use sic_dictionaries
  use sic_types
  use gbl_message
  use gbl_format
  !---------------------------------------------------------------------
  ! @ private
  !  Decode operands
  !---------------------------------------------------------------------
  character(len=*),       intent(in)  :: chain  ! Chain containing operand
  integer(kind=4),        intent(in)  :: nch    ! Length of chain
  type(sic_descriptor_t)              :: descr  ! Operand descriptor  Output
  logical,                intent(out) :: error  ! Logical error flag
  ! Global
  include 'gbl_memory.inc'
  ! Local
  character(len=*), parameter :: rname='MTH'
  integer(kind=address_length) :: ipnt
  integer(kind=4) :: ip,ier
  logical :: found
  real(kind=4) :: r4
  real(kind=8) :: r8
  integer(kind=8) :: i8
  real(kind=8) :: value  ! Used as an untyped buffer
  !
  error = .false.
  if (nch.eq.0) then
    error = .true.
    return
  endif
  !
  ! Removes closing parenthesis. Not very efficient: their index is known from
  ! GET_LEVEL
  ip = nch
  do while (ip.ge.1 .and. (chain(ip:ip).eq.')'.or. chain(ip:ip).eq.' '))
    ip = ip-1
  enddo
  if (ip.eq.0) then           ! Empty string after parenthesis stripp
    descr%type = 0            ! Undefined type
    descr%readonly = .false.  !
    descr%addr = 0            ! Null address
    descr%ndim = 0            ! Zero dimension
    descr%size = 0            ! Total size is zero
    descr%status = scratch_operand
    return
  endif
  ! Check for variables
  if (chain(1:1).lt.'A') then
    if (chain(1:1).ne.'"') then
      ! First try a logical
      if (chain(1:ip).eq.'.TRUE.') then
        call l4tol4(.true.,value,1)
        descr%type = fmt_l
        descr%readonly = .false.
        descr%size = 1
      elseif (chain(1:ip).eq.'.FALSE.') then
        call l4tol4(.false.,value,1)
        descr%type = fmt_l
        descr%readonly = .false.
        descr%size = 1
      else
        ! Second try a plain real number
        read (chain(1:ip),'(F30.0)',iostat=ier)  r8
        if (ier.ne.0) then
          error = .true.
          return
        endif
        if (sicprecis.eq.fmt_r8) then
          call r8tor8(r8,value,1)
          descr%type = fmt_r8
          descr%readonly = .false.
          descr%size = 2
        elseif (sicprecis.eq.fmt_r4) then
          r4 = sngl(r8)
          call r4tor4(r4,value,1)
          descr%type = fmt_r4
          descr%readonly = .false.
          descr%size = 1
        elseif (sicprecis.eq.fmt_i8) then
          i8 = nint(r8,kind=8)
          call i8toi8(i8,value,1)
          descr%type = fmt_i8
          descr%readonly = .false.
          descr%size = 2
        else  ! Use double precision for automatic sc
          call r8tor8(r8,value,1)
          descr%type = fmt_r8
          descr%readonly = .false.
          descr%size = 2
        endif
      endif
      ier = sic_getvm (descr%size,descr%addr)
      if (ier.ne.1) then
        call sic_message(seve%e,rname,'Memory allocation failure')
        error = .true.
        return
      endif
      ipnt = gag_pointer(descr%addr,memory)
      call w4tow4_sl(value,memory(ipnt),descr%size)
      descr%ndim = 0
      descr%status = scratch_operand
    else
      ! Starts with a double quote so it must be a character string.
      ! Build a descriptor
      if (chain(ip:ip).ne.'"' .or. ip.le.2) then
        error=.true.
        call sic_message(seve%e,rname,'Invalid character string '//chain(1:ip))
        return
      endif
      descr%type = ip-2
      descr%readonly = .false.
      descr%addr = locstr(chain)+1
      descr%ndim = 0
      descr%size = 1
      descr%status = readonly_operand
    endif
  else
    ! It must be a variable.
    found = .true.
    call sic_materialize (chain(1:ip),descr,found) ! Checked
    if (.not.found) then
      call sic_message(seve%e,rname,'Unknown variable '//chain(1:ip))
      error = .true.
    else
      ! Get a SICPRECIS incarnation of numerical variables. Moved
      ! to an evaluation routine for AUTOMATIC precision to work correctly.
      descr%readonly = .false.
      ! returned operand may be scratch because of a transposition!
      if (descr%status.ne.scratch_operand) descr%status = readonly_operand
      ! if (descr%type.ne.fmt_l.and.descr%type.le.0) then
      !   call sic_incarnate(sicprecis,descr,descr,error)
      ! endif
    endif
  endif
end subroutine read_operand
!
subroutine find_operator(line,nline,i_deb,i_fin,i_oper,code,preced,nfunarg,  &
  next,error)
  use sic_interfaces, except_this=>find_operator
  use sic_dictionaries
  use gbl_message
  !---------------------------------------------------------------------
  ! @ private
  !  Search for first operator in line
  !---------------------------------------------------------------------
  character(len=*), intent(in)  :: line        ! The line to be analysed
  integer(kind=4),  intent(in)  :: nline       ! Length of line
  integer(kind=4),  intent(out) :: i_deb       ! Start of operand if any
  integer(kind=4),  intent(out) :: i_fin       ! End of operand if any
  integer(kind=4),  intent(out) :: i_oper      ! End of operator
  integer(kind=4),  intent(out) :: code        ! Code of operator
  integer(kind=4),  intent(out) :: preced      ! Precedence of operator
  integer(kind=4),  intent(out) :: nfunarg(2)  ! Min-max number of arguments
  integer(kind=4),  intent(out) :: next        ! Start of non analysed part
  logical,          intent(out) :: error       ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='MTH'
  integer(kind=4) :: i,n,is,ip
  logical :: finished,number,in_chain,inchain
  !
  ! Various initialisations
  code = code_nop
  i_deb = 0
  i_fin = 0
  n = nline
  finished = .false.
  error = .false.
  in_chain = .false.
  inchain = .false.  ! For function start_operator
  !
  ! Remove opening parenthesis. Quite unefficient...
  do i=1,nline
    if (line(i:i).ne.'('.and.line(i:i).ne.' ') then
      is = i
      goto 1
    endif
  enddo
  ! Generate an error if line only contains opening parenthesis. Should not
  ! occur with present logic, but let us play safe.
  call sic_message(seve%e,rname,'Invalid syntax')
  error = .true.
  return
  !
1 continue
  !
  ! Check for unary + and -
  if ( (line(is:is).eq.'+'.or.line(is:is).eq.'-')) then
    ! + or - in front of a letter: real unary operator
    if ( (line(is+1:is+1).gt.'9'.or.line(is+1:is+1).lt.'0') .and.  &
          line(is+1:is+1).ne.'.') then
      if (line(is:is).eq.'+') then
        code = code_uplus
      elseif (line(is:is).eq.'-') then
        code = code_uminus
      else
        call sic_message(seve%e,rname,  &
          'Internal logic error, sign is '//line(is:is))
        goto 99
      endif
      preced = 5
      nfunarg = 1
      next = is+1
      i_oper = is
      return
      ! + or - is part of a signed number
    else
      number = .true.
      ip = is
      is = is+1
    endif
    ! Line does not start on a + or -
  else
    if ( (line(is:is).gt.'9'.or.line(is:is).lt.'0') .and.  &
          line(is:is).ne.'.') then
      number = .false.
    else
      number = .true.
    endif
    ip = is
  endif
  !
  ! Now, starts looking for real operators (others than unary + and -)
  do while(.not.finished)
    i_oper = start_operator(line,is,nline,inchain,error)
    if (error) goto 99
    if (i_oper.eq.0.or.i_oper.eq.nline) then
      ! No operator in LINE. There is an operand
      i_deb = ip
      i_fin = nline
      code  = code_nop
      nfunarg  = 0
      next  = nline + 1
      finished = .true.
      preced = 0
    else
      ! One possible operator was found
      if (line(i_oper:i_oper).eq.'"') then
        in_chain = .not.in_chain
        is = i_oper + 1
      elseif (in_chain) then
        is = i_oper + 1
      else
        !
        ! '*', '**' or '^', and '/' or '|' cannot be part of a number.
        if ( line(i_oper:i_oper).eq.'*' ) then
          finished = .true.
          i_deb = ip
          i_fin = i_oper-1
          if (line(i_oper+1:i_oper+1).eq.'*') then
            next = i_oper+2
            i_oper = i_oper + 1
            code = code_power
            preced = 6
          else
            next = i_oper+1
            code = code_mul
            preced = 5
          endif
          nfunarg = 2
        elseif (line(i_oper:i_oper).eq.'^') then
          finished = .true.
          i_deb = ip
          i_fin = i_oper-1
          next  = i_oper+1
          code = code_power
          preced = 6
          nfunarg = 2
        elseif (line(i_oper:i_oper).eq.'/' .or.  &
                line(i_oper:i_oper).eq.'|') then
          finished = .true.
          i_deb = ip
          i_fin = i_oper-1
          next  = i_oper+1
          code = code_div
          preced = 5
          nfunarg = 2
          !
          ! '+' and '-' could be part of a number
        elseif (line(i_oper:i_oper).eq.'+' .or.  &
                line(i_oper:i_oper).eq.'-') then
          ! In a number after 'E' and 'D': this is 1.D-8 or so. Ignore it
          if (number .and. (line(i_oper-1:i_oper-1).eq.'E' .or.  &
                            line(i_oper-1:i_oper-1).eq.'D')) then
            is = i_oper + 1
            ! Otherwise, this is a binary + or -
          else
            finished = .true.
            i_deb = ip
            i_fin = i_oper-1
            next  = i_oper+1
            if (line(i_oper:i_oper).eq.'+') code = code_bplus
            if (line(i_oper:i_oper).eq.'-') code = code_bminus
            preced = 4
            nfunarg = 2
          endif
          !
          ! This is a function call, NOT an array element !...
        elseif (line(i_oper:i_oper).eq.'(') then
          call get_funcode(line(ip:i_oper-1),code,nfunarg,error)
          if (error) goto 99
          finished = .true.
          i_deb = 0
          i_fin = 0
          preced = 8
          next = i_oper+1
        elseif (line(i_oper:i_oper).eq.',') then
          code = code_coma
          nfunarg = 0
          finished = .true.
          i_deb = ip
          i_fin = i_oper-1
          preced = 0
          next = i_oper+1
        elseif (line(i_oper:i_oper).eq.'.') then
          ! Found a dot: this could be the start of a logical or relational
          ! operator, but also part of a number
          !   or even worse, in some cases, part of a name to be tested
          !   as a variable name...
          i = start_operator(line,i_oper+1,n,inchain,error)
          if (error) goto 99
          if (i.le.(i_oper+2)) then
            ! No operator found (i==0) after the dot, or found a candidate
            ! at i_oper+1 or i_oper+2 => current dot is part of a number
            is = i_oper + 1
          elseif (line(i:i).ne.'.') then
            ! Next operator is not a dot: this is not a logical operator
            is = i_oper + 1
            ! #1 12-May-1988 S.G.
          elseif (line(i_oper+1:i_oper+1).ge.'0' .and.  &
                  line(i_oper+1:i_oper+1).le.'9' ) then
            ! Mantissa
            is = i_oper + 1
          elseif (line(i_oper+1:i_oper+1).eq.'D') then
            ! Part of exponent D something
            is = i_oper + 1
          elseif (line(i_oper+1:i_oper+1).eq.'E' .and.  &
                  line(i_oper+2:i_oper+2).ne.'Q') then
            ! Part of exponent E something
            is = i_oper + 1
            ! #1 End
          elseif (line(i_oper+1:i_oper+1).ne.'T' .and.  &
                  line(i_oper+1:i_oper+1).ne.'F' ) then
            ! The above test is presumably to check for .TRUE. or .FALSE.
            ! but why not also avoid other cases ?
            call get_logcode(line(i_oper+1:i-1),code,nfunarg,error)
            if (error) goto 99
            finished = .true.
            i_deb = ip
            i_fin = i_oper-1
            if (code.ge.code_ne) then
              preced = 3
            elseif (code.eq.code_not) then
              preced = 2
            else
              preced = 1
            endif
            next = i+1
          else
            is = i_oper + 1
          endif
          ! Do nothing for closing parenthesis and spaces
        else
          is = i_oper + 1
        endif
      endif
    endif
  enddo
  return
  !
99 error = .true.
  return
  !
contains
  !
  function start_operator(line,is,n,inchain,error)
    !---------------------------------------------------------------------
    !  Returns index of first character in line which could be part of an
    ! operator field. Return 0 if no candidate is found.
    !---------------------------------------------------------------------
    integer(kind=4) :: start_operator  ! Function value on return
    character(len=*), intent(in)    :: line     ! Character string to be formatted
    integer(kind=4),  intent(in)    :: is       ! Index of first character in line
    integer(kind=4),  intent(in)    :: n        ! Length of C
    logical,          intent(inout) :: inchain  !
    logical,          intent(out)   :: error    !
    ! Local
    integer(kind=4) :: i
    logical :: array,separator
    !
    error = .false.
    array = .false.
    separator = .false.
    start_operator = 0
    do i = is,n
      ! ( ) * + , - . / are contiguous in ASCII
      if (line(i:i).eq.'"') then
        ! Like e.g. in:  SAY 'GILDAS_NODE.eq."A/B"'
        ! The / is not an operator because it is in a string.
        start_operator = i
        inchain = .not.inchain
        return
      elseif (.not.inchain) then
        if (array) then
          if (line(i:i).eq.']') then
            array = .false.
            separator = .true.
          endif
        elseif (line(i:i).eq.'[') then
          array = .true.
        elseif (line(i:i).eq.']') then
          call sic_message(seve%e,rname,'Unmatch Closing bracket')
          error = .true.
          return
        elseif (line(i:i).ge.'('.and.line(i:i).le.'/') then
          ! For optimisation, use the fact that most possible operators are
          ! contiguous in the ASCII character set. Should be changed on a
          ! non-ASCII machine. Characters are ( ) + - * , . /
          start_operator = i
          return
        elseif (line(i:i).eq.'|') then
          start_operator = i
          return
        elseif (line(i:i).eq.'^') then
          start_operator = i
          return
        elseif (line(i:i).eq.' ') then
          start_operator = i
          return
        elseif (separator) then
          call sic_message(seve%e,rname,'Missing operator after closing bracket')
          error = .true.
          return
        endif
      endif
    enddo
  end function start_operator
  !
end subroutine find_operator
!
subroutine get_logcode(chain,i_code,nfunarg,error)
  use sic_interfaces, except_this=>get_logcode
  use sic_dictionaries
  use gbl_message
  !---------------------------------------------------------------------
  ! @ private
  !  Sends back a code for the logical or relational operator contained
  ! in CHAIN
  !---------------------------------------------------------------------
  character(len=*), intent(in)  :: chain       ! The line to be analysed
  integer(kind=4),  intent(out) :: i_code      ! Code for operator
  integer(kind=4),  intent(out) :: nfunarg(2)  ! Min-max number of arguments
  logical,          intent(out) :: error       ! Logical error flag
  ! Local
  character(len=*), parameter :: rname='MTH'
  !
  ! Logical operators
  error = .false.
  nfunarg = 2
  select case (chain)
  case ('OR')
    i_code = code_or
  case ('AND')
    i_code = code_and
  case ('NOT')
    i_code = code_not
    nfunarg = 1
  !
  ! Relational operators
  case ('NE')
    i_code = code_ne
  case ('EQ')
    i_code = code_eq
  case ('LE')
    i_code = code_le
  case ('LT')
    i_code = code_lt
  case ('GE')
    i_code = code_ge
  case ('GT')
    i_code = code_gt
  case default
    call sic_message(seve%e,rname,  &
      'Unknown logical or relational operator .'//chain//'.')
    error = .true.
  end select
end subroutine get_logcode
!
subroutine get_level(line,n,level,coma,error)
  use sic_interfaces, except_this=>get_level
  use sic_expressions
  use gbl_message
  !---------------------------------------------------------------------
  ! @ private
  !  Could be done while removing blanks and tabs...
  ! Determines the parenthesis level for each character of string LINE.
  !    LEVEL(I) is the level of LINE(I:I)
  ! Parenthesis themselves are stored at the level below their contents. 
  ! Comas are also stored one level below their surroundings.
  !    COMA(I) is defined for opening parenthesis as the number of comas
  ! at their level before drop to lower levels. For a function call, it
  ! should correspond to the number of arguments minus 1.
  !---------------------------------------------------------------------
  character(len=*), intent(in)  :: line      ! The line to be analysed
  integer(kind=4),  intent(in)  :: n         ! Length of LINE
  integer(kind=4)               :: level(n)  ! Parenthesis level array
  integer(kind=4)               :: coma(n)   !
  logical,          intent(out) :: error     !
  ! Local
  character(len=*), parameter :: rname='SIC'
  integer(kind=4) :: prev_opening(maxpar+1)
  integer(kind=4) :: i,curr_level
  logical :: array
  !
  error = .false.
  curr_level = 0
  array = .false.
  do i=1,n
    if (line(i:i).eq.'[') then
      array = .true.
    elseif (array) then
      if (line(i:i).eq.']') array = .false.
    elseif (line(i:i).eq.'(') then
      level(i) = curr_level
      curr_level = curr_level+1
      prev_opening(curr_level) = i
      coma(i) = 0
    elseif (line(i:i).eq.')') then
      curr_level = curr_level-1
      if (curr_level.lt.0) then
        call sic_message(seve%e,rname,'Unmatched closing parenthesis')
        goto 99
      endif
      level(i) = curr_level
    elseif (line(i:i).eq.',') then
      if (curr_level.le.0) then
        call sic_message(seve%e,rname,'Extra coma, check syntax')
        goto 99
      endif
      level(i) = curr_level-1
      coma(prev_opening(curr_level)) = coma(prev_opening(curr_level)) + 1
    else
      level(i) = curr_level
    endif
  enddo
  if (curr_level.gt.0) then
    call sic_message(seve%e,'SIC','Unmatched opening parenthesis')
    goto 99
  endif
  return
  !
  ! Error return
99 error = .true.
end subroutine get_level
