######################################################################
# Funcao principal (publica)

def analisa_comando(C):
  """Analisa a cadeia {C} esperando que a mesma contenha um comando 
  valido.  Se nao contiver, escreve na tela uma mensagem de erro 
  e devolve {None}.  Se contiver, devolve uma arvore de operadores.
  
  Cada noh da arvore pode ser um numero float, um nome de variavel 
  ('x', 'val2', etc., ou um dicionario com chaves 'op', 'arg1', 'arg2'
  e 'arg3'.  
  
  Neste ultimo caso, o valor do par 'op' eh uma cadeia com a operacao 
  a executar: '+', '-', '*', '/', 'sqrt', 'log', etc.  Os valores de 
  'arg1', 'arg2', e 'arg3' sao arvores dos operandos da opoeracao.
  Se a operacao tem menos de 3 operandos, os ultimos sao {None}. """
  
  (A,k) = analisa_atribuicao(C,0)
  if (A != None) and (k != len(C)):
    print("** Caracteres sobrando no fim do comando: '" + C[k:] + "'")
    return None
  elif A == None:
    print("** Comando invalido")
    return None
  else:
    return A
  ##----------------------------------------------------------------------

######################################################################
# Funcoes de analise sintatica {analisa_{XXX}} (internas):

# Cada uma das funcoes abaixo recebe uma cadeia {C} e um inteiro {k}.
# Ela tenta analisar um certo tipo {XXX} de sub-formula comecando na posicao 
# {C[k]}, possivelmente com brancos no comeco.  
# Se encontrar essa coisa, devolve um par {(A,m)} onde 
# {A} eh a arvore dessa coisa e {m} eh o indice do primeiro caracter
# de {C} que nao eh parte da coisa analisada.  Se nao encontrar
# essa coisa, devolve {(None,k)}. 
  
def analisa_atribuicao(C,k):
  """Analisa a cadeia {C} a partir do caracter {C[k]}, esperando
  encontrar '<variavel>=<formula>' ou apenas '<formula>'. """
  # Tenta analisar '<variavel> = <formula>'
  (A1,m1) = analisa_variavel(C,k)
  if A1 != None:
    (op,mo) = pega_operador(C,m1,["="])
    if op != None:
      # print("[asa: '" + A1 + "', op = '" + op + "' mo = ", mo, "]")
      assert op == '='
      (A2,m3) = analisa_soma(C,mo)
      if A2 != None:
        A = constroi_arvore('=', A1, A2, None)
        return (A, m3)
      else:
        # print("[asa: no formula]")
    else:
      # print("[asa: no op]") 
  
  # Nao deu certo. Tenta apenas '<formula>'
  (A4,m4) = analisa_soma(C,k)
  if A4 != None:
    return (A4, m4)
  
  # Nem um ne outro:
  return (None,k)

  ##----------------------------------------------------------------------
    
def analisa_soma(C,k):
  """Analisa uma '<soma>, que pode ser uma '<parcela>' opcionalmente 
  seguido de zero ou mais parcelas '+<parcela>'ou '-<parcela>'. """
  
  (A,m) = analisa_parcela(C,k)
  if A == None:
    # Nao conseguiu nem a primeira parcela:
    # print("[ass: no first parcela]")
    return (None,k)
  else:
    while True:
      # Neste ponto {A} e a arvore da soma/subtracao de todas as
      # parcelas jah analisadas, e {m} o primeiro 
      # caracter ainda nao analisado.
      (op,mo) = pega_operador(C,m,["+","-"])
      if op == None:
        # Nao achou operador de soma/subtracao, devolve o que achou:
        return (A,m)
      else:
        assert (op == '+') or (op == '-')  # Paranoia.
        # Parece que tem mais uma parcela:
        (P,m2) = analisa_parcela(C,mo)
        if P == None:
          # Achou um operador mas nao seguido de parcela. 
          # Devolve a soma encontrada antes do operador.
          return (A,m)
        # Achou mais uma parcela. Acrescenta aa arvore:
        A = constroi_arvore(op,A,P,None)
        m = m2
  ##----------------------------------------------------------------------
  
def analisa_parcela(C,k):
  """Procura uma '<parcela>', que pode ser um '<fator>' opcionalmente 
  seguido de zero ou mais '*<fator>' ou '/<fator>'. """
  (A,m) = analisa_fator(C,k)
  if A == None:
    # print("[asp: no first fator]")
    # Nao conseguiu nem o primeiro fator:
    return (None,k)
  else:
    while True:
      # Neste ponto {A} e a arvore do produto/quociente de todos os
      # fatores jah analisados, e {m} o primeiro 
      # caracter ainda nao analisado.
      (op,mo) = pega_operador(C,m,["*","/"])
      if op == None:
        # Nao achou operador de mult/div, devolve o que achou:
        return (A,m)
      else:
        assert (op == '*') or (op == '/') # Paranoia.
        # Parece que tem mais um fator:
        (F,m2) = analisa_fator(C,mo)
        if F == None:
          # Achou um operador mas nao seguido de fator. 
          # Devolve a parcela encontrada antes do operador.
          return (A,m)
        # Achou mais um fator. Acrescenta aa arvore:
        A = constroi_arvore(op,A,F,None)
        m = m2
  ##----------------------------------------------------------------------
  
def analisa_fator(C,k):
  """Analisa um '<fator>', que pode ser uma variavel,
  um numero, ou uma '<soma>' entre parenteses."""
  
  # Tenta variavel:
  (A,m) = analisa_variavel(C,k)
  if A != None:
    # print("[asf: variavel '" + A + "']")
    return (A,m)
  
  # Tenta numero:
  (A,m) = analisa_numero(C,k)
  if A != None:
    # print("[asf: numero '%f']" % A)
    return (A,m)
    
  # Tenta soma entre parenteses. Procura o abre parenteses: 
  (A,m) = analisa_parenteses(C,k)
  if A != None:
    # print("[asf: parenteses]")
    return (A,m)
    
  # Nao deu certo nada:
  return (None,k)
  
  ##----------------------------------------------------------------------
  
def analisa_parenteses(C,k):
  """Analisa uma somatoria entre parenteses."""
  
  (abre,ma) = pega_operador(C,k,["("])
  if abre == None:
    # Nao achou nem o abre parenteses:
    return (None,k)
  else:
    # Achou abre parenteses, procura somatoria:
    (A,mm) = analisa_soma(C,ma)
    if A == None:
      # Não tem somatoria:
      return (None,k)

    # Achou somatoria, procura fecha parenteses:
    i = pula_brancos(C,mm)
    (fecha,mf) = pega_operador(C,i,[")"])
    if fecha == None:
      # Nao achou o fecha parenteses:
      return (None,k)

    # Achou tudo:
    return (A,mf)

  ##----------------------------------------------------------------------

def analisa_variavel(C,k):
  """Analisa um nome de variavel, consistindo de 
  uma letra seguida de zero ou mais letras, algarismos, ou '_'.
  Se encontrar, a arvore devolvida eh o nome da variavel,
  na forma de uma cadeia."""
  
  i = pula_brancos(C,k) # Indice do proximo a analisar
  if eh_letra(C,i):
    # Pega primeira letra: 
    nomvar = C[i]
    i = i+1
    # Pega resto do nome:
    while eh_letra_digito_sublin(C,i):
      nomvar = nomvar + C[i]
      i = i + 1
    # Acabou o nome:
    return (nomvar,i)
  else:
    # Nao achou a primeira letra:
    return (None,k)
      
  ##----------------------------------------------------------------------
  
def analisa_numero(C,k):
  """Analisa um numero em notacao decimal, com ou sem
  sinal.  Por enquanto, o numero deve ser inteiro.
  Se encontrar, a arvore devolvida eh o valor {float}
  do numero."""
  
  # Procura sinal, converte para {+1} ou {-1}:
  i = pula_brancos(C,k)
  (sn,ms) = pega_operador(C,i,["+","-"])
  if sn == None:
    sinal = +1
    assert ms == i
  elif sn == "+":
    sinal = +1
  else:
    sinal = -1
  
  # Pega algarismos a partir da posicao {ms}:
  i = ms # Indice do proximo a analisar
  if eh_digito(C,i):
    # Achou primeiro algarismo, converte para inteiro:
    numval = ord(C[i]) - ord('0')
    i = i + 1
    # Cata mais algarismos, se houver:
    while eh_digito(C,i):
      # Acrescenta digito ao valor:
      digval = ord(C[i]) - ord('0')
      numval = numval*10 + digval
      i = i + 1
    # Retorna o que achou:
    return (float(sinal*numval),i)
  else:
    # Nao achou nem o primeiro algarismo:
    return (None,k)
  
  ##----------------------------------------------------------------------
  
######################################################################
# Ferramentas uteis (internas):

def pula_brancos(C,k):
  """Supoe que {C} eh uma cadeia e {k} eh um inteiro nao negativo.
  Devolve o primeiro indice {m} maior ou igual a {k}
  tal que {C[k]} eh um caracter nao branco, ou nao existe."""

  n = len(C)
  m = k
  while m < n and C[m] == " ":
    m = m + 1
  return m
  ##----------------------------------------------------------------------
  
def eh_letra(C,k):
  """Supoe que {C} eh uma cadeia e {k} eh um inteiro nao negativo.
  Verifica se {C[k]} existe e eh letra maiuscula ou minuscula."""
  if k >= len(C): return False
  if C[k] >= "a" and C[k] <= "z": return True
  if C[k] >= "A" and C[k] <= "Z": return True
  return False
  ##----------------------------------------------------------------------
  
def eh_digito(C,k):
  """Supoe que {C} eh uma cadeia e {k} eh um inteiro nao negativo.
  Verifica se {C[k]} existe e eh algarismo decimal."""
  if k >= len(C): return False
  if C[k] >= "0" and C[k] <= "9": return True
  return False
  ##----------------------------------------------------------------------
  
def eh_letra_digito_sublin(C,k):
  """Supoe que {C} eh uma cadeia e {k} eh um inteiro nao negativo.
  Verifica se {C[k]} existe e eh letra, algarismo decimal, ou '_'."""
  if k >= len(C): return False
  if eh_letra(C,k): return True
  if eh_digito(C,k): return True
  if C[k] == '_': return True
  return False
  ##----------------------------------------------------------------------

def pega_operador(C,k,L):
  """Supoe que {C} eh uma cadeia, {k} eh um inteiro nao negativo,
  e {L} uma lista de caracteres (cadeias de comprimento 1). 
  Procura um desses caracteres na cadeia, a partir da posicao {k},
  ignorando brancos.  Se encontrar, devolve um par {(op,m)} onde 
  {op} eh o caracter encontrado e {m} eh o indice do primeiro caracter
  de {C} depois desse caracter.  Se nao encontrar,, devolve {(None,k)}. """
  
  i = pula_brancos(C,k) # Acha primeiro caracter nao branco {C[i]}.
  if i >= len(C):
    # Acabou a cadeia:
    return (None,k)
  else:
    # O caracter {C[i]} existe, procura em {L}:
    for op in L:
      if C[i] == op:
        return (op,i+1)
    # Nao achou:
    return (None,k)
  
  ##----------------------------------------------------------------------

def constroi_arvore(op,arg1,arg2,arg3):
  """Constroi um noh de operacao com esses campos."""
  return { "op":op, "arg1":arg1, "arg2":arg2, "arg3": arg3}
  ##----------------------------------------------------------------------
  
# Last edited on 2018-10-16 20:06:20 by stolfilocal
