# ================================
#   BLang - Linguagem do Bernardo
#   100% original, sem copyright
# ================================

import random as _random

TOKEN_NUMERO   = "NUMERO"
TOKEN_TEXTO    = "TEXTO"
TOKEN_BOOL     = "BOOL"
TOKEN_NOME     = "NOME"
TOKEN_OP       = "OP"
TOKEN_COMP     = "COMP"
TOKEN_IGUAL    = "IGUAL"
TOKEN_ABRE     = "ABRE"
TOKEN_FECHA    = "FECHA"
TOKEN_VIRGULA  = "VIRGULA"
TOKEN_FIM      = "FIM"

PALAVRAS_CHAVE = {"fala","var","se","entao","senao","fim","repete","vezes","funcao","retorna","e","ou","nao","pergunta"}

def tokenizar(codigo: str) -> list:
    tokens = []
    i = 0
    while i < len(codigo):
        c = codigo[i]
        if c in " \t\r": i += 1; continue
        if c == "#":
            while i < len(codigo) and codigo[i] != "\n": i += 1
            continue
        if c == "\n":
            if tokens and tokens[-1][0] != TOKEN_FIM: tokens.append((TOKEN_FIM, "\n"))
            i += 1; continue
        if c.isdigit() or (c == "-" and i+1 < len(codigo) and codigo[i+1].isdigit()):
            j = i+1; tem_ponto = False
            while j < len(codigo) and (codigo[j].isdigit() or (codigo[j] == "." and not tem_ponto)):
                if codigo[j] == ".": tem_ponto = True
                j += 1
            val = float(codigo[i:j]) if tem_ponto else int(codigo[i:j])
            tokens.append((TOKEN_NUMERO, val)); i = j; continue
        if c == '"':
            j = i+1
            while j < len(codigo) and codigo[j] != '"': j += 1
            tokens.append((TOKEN_TEXTO, codigo[i+1:j])); i = j+1; continue
        if c in "!<>=" and i+1 < len(codigo) and codigo[i+1] == "=":
            tokens.append((TOKEN_COMP, c+"=")); i += 2; continue
        if c in "<>": tokens.append((TOKEN_COMP, c)); i += 1; continue
        if c == "=": tokens.append((TOKEN_IGUAL, "=")); i += 1; continue
        if c in "+-*/%": tokens.append((TOKEN_OP, c)); i += 1; continue
        if c == "(": tokens.append((TOKEN_ABRE, "(")); i += 1; continue
        if c == ")": tokens.append((TOKEN_FECHA, ")")); i += 1; continue
        if c == ",": tokens.append((TOKEN_VIRGULA, ",")); i += 1; continue
        if c.isalpha() or c == "_":
            j = i
            while j < len(codigo) and (codigo[j].isalnum() or codigo[j] == "_"): j += 1
            palavra = codigo[i:j]
            if palavra in ("verdade", "mentira"): tokens.append((TOKEN_BOOL, palavra == "verdade"))
            elif palavra in PALAVRAS_CHAVE: tokens.append((palavra, palavra))
            else: tokens.append((TOKEN_NOME, palavra))
            i = j; continue
        raise SyntaxError(f"[BLang] Caractere desconhecido: '{c}'")
    if tokens and tokens[-1][0] != TOKEN_FIM: tokens.append((TOKEN_FIM, "\n"))
    return tokens


class _Retorno(Exception):
    def __init__(self, valor): self.valor = valor


class BLang:
    def __init__(self):
        self.variaveis: dict = {}
        self.funcoes: dict = {}
        self.tokens: list = []
        self.pos: int = 0

    def atual(self):
        return self.tokens[self.pos] if self.pos < len(self.tokens) else (TOKEN_FIM, None)

    def consumir(self, tipo=None):
        tok = self.atual()
        if tipo and tok[0] != tipo: raise SyntaxError(f"[BLang] Esperava '{tipo}', recebi '{tok[0]}' ({tok[1]})")
        self.pos += 1; return tok

    def pular_fins(self):
        while self.atual()[0] == TOKEN_FIM: self.pos += 1

    def acabou(self): return self.pos >= len(self.tokens)

    def executar(self, codigo: str):
        self.tokens = tokenizar(codigo); self.pos = 0
        resultado = None
        while not self.acabou():
            self.pular_fins()
            if self.acabou(): break
            resultado = self.instrucao()
        return resultado

    def instrucao(self):
        tok = self.atual()
        if tok[0] == "fala":    return self.cmd_fala()
        if tok[0] == "pergunta": return self.cmd_pergunta()
        if tok[0] == "var":     return self.cmd_var()
        if tok[0] == "se":      return self.cmd_se()
        if tok[0] == "repete":  return self.cmd_repete()
        if tok[0] == "funcao":  return self.cmd_def_funcao()
        if tok[0] == "retorna": return self.cmd_retorna()
        if tok[0] == TOKEN_NOME: return self.cmd_nome()
        if tok[0] == TOKEN_FIM: self.consumir(); return None
        raise SyntaxError(f"[BLang] Instrução desconhecida: '{tok[1]}'")

    def cmd_fala(self):
        self.consumir("fala"); valor = self.expressao(); self.consumir_fim()
        print(valor); return valor

    def cmd_pergunta(self):
        self.consumir("pergunta"); msg = self.expressao(); self.consumir_fim()
        return input(str(msg))

    def cmd_var(self):
        self.consumir("var"); nome = self.consumir(TOKEN_NOME)[1]
        # suporta: var x = pergunta "msg" ou var x = expressao
        if self.atual()[0] == TOKEN_IGUAL:
            self.consumir(TOKEN_IGUAL)
            if self.atual()[0] == "pergunta":
                self.consumir("pergunta"); msg = self.expressao(); self.consumir_fim()
                valor = input(str(msg))
            else:
                valor = self.expressao(); self.consumir_fim()
        else:
            valor = None; self.consumir_fim()
        self.variaveis[nome] = valor; return valor

    def cmd_nome(self):
        nome = self.consumir(TOKEN_NOME)[1]
        if self.atual()[0] == TOKEN_IGUAL:
            self.consumir(TOKEN_IGUAL)
            if self.atual()[0] == "pergunta":
                self.consumir("pergunta"); msg = self.expressao(); self.consumir_fim()
                valor = input(str(msg))
            else:
                valor = self.expressao(); self.consumir_fim()
            if nome not in self.variaveis: raise NameError(f"[BLang] Variável '{nome}' não existe. Use 'var' primeiro.")
            self.variaveis[nome] = valor; return valor
        if self.atual()[0] == TOKEN_ABRE:
            resultado = self.chamar_funcao(nome); self.consumir_fim(); return resultado
        raise SyntaxError(f"[BLang] Uso inválido de '{nome}'")

    def cmd_se(self):
        self.consumir("se"); condicao = self.expressao_logica()
        self.consumir("entao"); self.consumir_fim_opcional()
        bloco_se = []; bloco_senao = []; em_senao = False
        while True:
            self.pular_fins()
            if self.atual()[0] == "senao": self.consumir("senao"); self.consumir_fim_opcional(); em_senao = True; continue
            if self.atual()[0] == "fim": break
            linha = self.coletar_linha()
            (bloco_senao if em_senao else bloco_se).append(linha)
        self.consumir("fim"); self.consumir_fim()
        for linha in (bloco_se if condicao else bloco_senao): self.executar_bloco(linha)

    def cmd_repete(self):
        self.consumir("repete"); vezes = self.expressao()
        self.consumir("vezes"); self.consumir_fim_opcional()
        linhas = []
        while True:
            self.pular_fins()
            if self.atual()[0] == "fim": break
            linhas.append(self.coletar_linha())
        self.consumir("fim"); self.consumir_fim()
        for _ in range(int(vezes)):
            for linha in linhas: self.executar_bloco(linha)

    def cmd_def_funcao(self):
        self.consumir("funcao"); nome = self.consumir(TOKEN_NOME)[1]
        self.consumir(TOKEN_ABRE); params = []
        while self.atual()[0] != TOKEN_FECHA:
            params.append(self.consumir(TOKEN_NOME)[1])
            if self.atual()[0] == TOKEN_VIRGULA: self.consumir(TOKEN_VIRGULA)
        self.consumir(TOKEN_FECHA); self.consumir_fim_opcional()
        linhas = []
        while True:
            self.pular_fins()
            if self.atual()[0] == "fim": break
            linhas.append(self.coletar_linha())
        self.consumir("fim"); self.consumir_fim()
        self.funcoes[nome] = (params, linhas)

    def cmd_retorna(self):
        self.consumir("retorna"); valor = self.expressao(); self.consumir_fim()
        raise _Retorno(valor)

    def expressao_logica(self):
        esq = self.expressao_comparacao()
        while self.atual()[0] in ("e", "ou"):
            op = self.consumir()[1]; dir = self.expressao_comparacao()
            esq = esq and dir if op == "e" else esq or dir
        return esq

    def expressao_comparacao(self):
        if self.atual()[0] == "nao": self.consumir("nao"); return not self.expressao_comparacao()
        esq = self.expressao()
        if self.atual()[0] == TOKEN_COMP:
            op = self.consumir(TOKEN_COMP)[1]; dir = self.expressao()
            if op == "==": return esq == dir
            if op == "!=": return esq != dir
            if op == "<":  return esq < dir
            if op == ">":  return esq > dir
            if op == "<=": return esq <= dir
            if op == ">=": return esq >= dir
        return bool(esq)

    def expressao(self):
        esq = self.termo()
        while self.atual()[0] == TOKEN_OP and self.atual()[1] in "+-":
            op = self.consumir(TOKEN_OP)[1]; dir = self.termo()
            esq = (str(esq)+str(dir)) if (isinstance(esq,str) or isinstance(dir,str)) and op=="+" else esq+dir if op=="+" else esq-dir
        return esq

    def termo(self):
        esq = self.fator()
        while self.atual()[0] == TOKEN_OP and self.atual()[1] in "*/%":
            op = self.consumir(TOKEN_OP)[1]; dir = self.fator()
            if op == "*": esq = esq * dir
            elif op == "/":
                if dir == 0: raise ZeroDivisionError("[BLang] Divisão por zero!")
                esq = esq / dir
            else: esq = esq % dir
        return esq

    def fator(self):
        tok = self.atual()
        if tok[0] == TOKEN_NUMERO: self.consumir(); return tok[1]
        if tok[0] == TOKEN_TEXTO:  self.consumir(); return tok[1]
        if tok[0] == TOKEN_BOOL:   self.consumir(); return tok[1]
        if tok[0] == TOKEN_ABRE:
            self.consumir(TOKEN_ABRE); val = self.expressao_logica(); self.consumir(TOKEN_FECHA); return val
        if tok[0] == TOKEN_NOME:
            nome = self.consumir(TOKEN_NOME)[1]
            if self.atual()[0] == TOKEN_ABRE: return self.chamar_funcao(nome)
            if nome not in self.variaveis: raise NameError(f"[BLang] Variável '{nome}' não existe.")
            return self.variaveis[nome]
        if tok[0] == TOKEN_OP and tok[1] == "-": self.consumir(); return -self.fator()
        raise SyntaxError(f"[BLang] Valor inválido: '{tok[1]}'")

    def chamar_funcao(self, nome):
        if nome == "texto":    self.consumir(TOKEN_ABRE); v=self.expressao(); self.consumir(TOKEN_FECHA); return str(v)
        if nome == "num":
            self.consumir(TOKEN_ABRE); v=self.expressao(); self.consumir(TOKEN_FECHA)
            return int(v) if (isinstance(v,str) and v.lstrip("-").isdigit()) else float(v) if isinstance(v,str) else int(v)
        if nome == "tipo":     self.consumir(TOKEN_ABRE); v=self.expressao(); self.consumir(TOKEN_FECHA); return type(v).__name__
        if nome == "tamanho":  self.consumir(TOKEN_ABRE); v=self.expressao(); self.consumir(TOKEN_FECHA); return len(str(v))
        if nome == "aleatorio":
            self.consumir(TOKEN_ABRE); a=self.expressao(); self.consumir(TOKEN_VIRGULA); b=self.expressao(); self.consumir(TOKEN_FECHA)
            return _random.randint(int(a), int(b))
        if nome not in self.funcoes: raise NameError(f"[BLang] Função '{nome}' não existe.")
        params, linhas = self.funcoes[nome]
        self.consumir(TOKEN_ABRE); args = []
        while self.atual()[0] != TOKEN_FECHA:
            args.append(self.expressao())
            if self.atual()[0] == TOKEN_VIRGULA: self.consumir(TOKEN_VIRGULA)
        self.consumir(TOKEN_FECHA)
        if len(args) != len(params): raise TypeError(f"[BLang] '{nome}' espera {len(params)} arg(s), recebeu {len(args)}.")
        vars_antes = self.variaveis.copy()
        for p, a in zip(params, args): self.variaveis[p] = a
        resultado = None
        try:
            for linha in linhas: self.executar_bloco(linha)
        except _Retorno as r: resultado = r.valor
        self.variaveis = vars_antes; return resultado

    def coletar_linha(self) -> list:
        inicio = self.pos
        while self.pos < len(self.tokens) and self.tokens[self.pos][0] != TOKEN_FIM: self.pos += 1
        if self.pos < len(self.tokens): self.pos += 1
        return self.tokens[inicio:self.pos]

    def executar_bloco(self, tokens_linha: list):
        st, sp = self.tokens, self.pos
        self.tokens = tokens_linha; self.pos = 0
        try:
            while not self.acabou():
                self.pular_fins()
                if self.acabou(): break
                self.instrucao()
        finally: self.tokens = st; self.pos = sp

    def consumir_fim(self):
        if self.atual()[0] == TOKEN_FIM: self.consumir()

    def consumir_fim_opcional(self):
        if self.atual()[0] == TOKEN_FIM: self.consumir()
