"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Lexador = void 0;
const browser_process_hrtime_1 = __importDefault(require("browser-process-hrtime"));
const simbolo_1 = require("./simbolo");
const palavras_reservadas_1 = require("./palavras-reservadas");
const delegua_1 = __importDefault(require("../tipos-de-simbolos/delegua"));
/**
 * O Lexador é responsável por transformar o código em uma coleção de tokens de linguagem.
 * Cada token de linguagem é representado por um tipo, um lexema e informações da linha de código em que foi expresso.
 * Também é responsável por mapear as palavras reservadas da linguagem, que não podem ser usadas por outras
 * estruturas, tais como nomes de variáveis, funções, literais, classes e assim por diante.
 */
class Lexador {
    constructor(performance = false) {
        this.performance = performance;
        this.simbolos = [];
        this.erros = [];
        this.hashArquivo = -1;
        this.inicioSimbolo = 0;
        this.atual = 0;
        this.linha = 0;
    }
    eDigito(caractere) {
        return caractere >= '0' && caractere <= '9';
    }
    eAlfabeto(caractere) {
        const acentuacoes = [
            'á',
            'Á',
            'ã',
            'Ã',
            'â',
            'Â',
            'à',
            'À',
            'é',
            'É',
            'ê',
            'Ê',
            'í',
            'Í',
            'ó',
            'Ó',
            'õ',
            'Õ',
            'ô',
            'Ô',
            'ú',
            'Ú',
            'ç',
            'Ç',
            '_',
        ];
        return ((caractere >= 'a' && caractere <= 'z') ||
            (caractere >= 'A' && caractere <= 'Z') ||
            acentuacoes.includes(caractere));
    }
    eAlfabetoOuDigito(caractere) {
        return this.eDigito(caractere) || this.eAlfabeto(caractere);
    }
    eFinalDaLinha() {
        if (this.codigo.length === this.linha) {
            return true;
        }
        return this.atual >= this.codigo[this.linha].length;
    }
    /**
     * Indica se o código está na última linha.
     * @returns Verdadeiro se contador de linhas está na última linha.
     *          Falso caso contrário.
     */
    eUltimaLinha() {
        return this.linha >= this.codigo.length - 1;
    }
    eFinalDoCodigo() {
        return this.eUltimaLinha() && this.codigo[this.codigo.length - 1].length <= this.atual;
    }
    avancar() {
        this.atual += 1;
        if (this.eFinalDaLinha() && !this.eUltimaLinha()) {
            this.linha++;
            this.atual = 0;
        }
    }
    adicionarSimbolo(tipo, literal = null) {
        const texto = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual);
        this.simbolos.push(new simbolo_1.Simbolo(tipo, literal || texto, literal, this.linha + 1, this.hashArquivo));
    }
    simboloAtual() {
        if (this.eFinalDaLinha())
            return '\0';
        return this.codigo[this.linha].charAt(this.atual);
    }
    comentarioMultilinha() {
        let conteudo = '';
        while (!this.eFinalDoCodigo()) {
            this.avancar();
            conteudo += this.codigo[this.linha].charAt(this.atual);
            if (this.simboloAtual() === '*' && this.proximoSimbolo() === '/') {
                const linhas = conteudo.split('\0');
                for (let linha of linhas) {
                    this.adicionarSimbolo(delegua_1.default.LINHA_COMENTARIO, linha.trim());
                }
                // Remove o asterisco da última linha
                let lexemaUltimaLinha = this.simbolos[this.simbolos.length - 1].lexema;
                lexemaUltimaLinha = lexemaUltimaLinha.substring(0, lexemaUltimaLinha.length - 1);
                this.simbolos[this.simbolos.length - 1].lexema = lexemaUltimaLinha;
                this.simbolos[this.simbolos.length - 1].literal = lexemaUltimaLinha;
                this.avancar();
                this.avancar();
                break;
            }
        }
    }
    comentarioUmaLinha() {
        this.avancar();
        const linhaAtual = this.linha;
        let ultimoAtual = this.atual;
        while (linhaAtual === this.linha && !this.eFinalDoCodigo()) {
            ultimoAtual = this.atual;
            this.avancar();
        }
        const conteudo = this.codigo[linhaAtual].substring(this.inicioSimbolo + 2, ultimoAtual);
        this.adicionarSimbolo(delegua_1.default.COMENTARIO, conteudo.trim());
    }
    proximoSimbolo() {
        return this.codigo[this.linha].charAt(this.atual + 1);
    }
    simboloAnterior() {
        return this.codigo[this.linha].charAt(this.atual - 1);
    }
    analisarTexto(delimitador = '"') {
        let textoCompleto = '';
        this.avancar();
        while (!this.eFinalDoCodigo()) {
            const caractere = this.simboloAtual();
            if (caractere === delimitador) {
                this.avancar();
                this.adicionarSimbolo(delegua_1.default.TEXTO, textoCompleto.replace(/\\n/g, '\n'));
                return;
            }
            if (caractere === '\0' && this.eUltimaLinha()) {
                this.erros.push({
                    linha: this.linha + 1,
                    caractere: this.simboloAnterior(),
                    mensagem: 'Texto não finalizado.',
                });
                return;
            }
            if (caractere === '\0') {
                textoCompleto += '\n';
                this.avancar();
                continue;
            }
            textoCompleto += caractere;
            this.avancar();
        }
        this.erros.push({
            linha: this.linha + 1,
            caractere: this.simboloAnterior(),
            mensagem: 'Texto não finalizado.',
        });
    }
    analisarNumero() {
        while (this.eDigito(this.simboloAtual())) {
            this.avancar();
        }
        if (this.simboloAtual() == '.' && this.eDigito(this.proximoSimbolo())) {
            this.avancar();
            while (this.eDigito(this.simboloAtual())) {
                this.avancar();
            }
        }
        const numeroCompleto = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual);
        this.adicionarSimbolo(delegua_1.default.NUMERO, parseFloat(numeroCompleto));
    }
    identificarPalavraChave() {
        while (this.eAlfabetoOuDigito(this.simboloAtual())) {
            this.avancar();
        }
        const codigo = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual);
        const tipo = codigo in palavras_reservadas_1.palavrasReservadasDelegua
            ? palavras_reservadas_1.palavrasReservadasDelegua[codigo]
            : delegua_1.default.IDENTIFICADOR;
        this.adicionarSimbolo(tipo);
    }
    analisarToken() {
        const caractere = this.simboloAtual();
        switch (caractere) {
            case '@':
                this.adicionarSimbolo(delegua_1.default.ARROBA, '@');
                this.avancar();
                break;
            case '[':
                this.adicionarSimbolo(delegua_1.default.COLCHETE_ESQUERDO, '[');
                this.avancar();
                break;
            case ']':
                this.adicionarSimbolo(delegua_1.default.COLCHETE_DIREITO, ']');
                this.avancar();
                break;
            case '(':
                this.adicionarSimbolo(delegua_1.default.PARENTESE_ESQUERDO, '(');
                this.avancar();
                break;
            case ')':
                this.adicionarSimbolo(delegua_1.default.PARENTESE_DIREITO, ')');
                this.avancar();
                break;
            case '{':
                this.adicionarSimbolo(delegua_1.default.CHAVE_ESQUERDA, '{');
                this.avancar();
                break;
            case '}':
                this.adicionarSimbolo(delegua_1.default.CHAVE_DIREITA, '}');
                this.avancar();
                break;
            case ',':
                this.adicionarSimbolo(delegua_1.default.VIRGULA, ',');
                this.avancar();
                break;
            case '.':
                this.adicionarSimbolo(delegua_1.default.PONTO, '.');
                this.avancar();
                break;
            case '-':
                this.inicioSimbolo = this.atual;
                this.avancar();
                if (this.simboloAtual() === '=') {
                    this.adicionarSimbolo(delegua_1.default.MENOS_IGUAL, '-=');
                    this.avancar();
                }
                else if (this.simboloAtual() === '-') {
                    this.adicionarSimbolo(delegua_1.default.DECREMENTAR, '--');
                    this.avancar();
                }
                else {
                    this.adicionarSimbolo(delegua_1.default.SUBTRACAO);
                }
                break;
            case '+':
                this.inicioSimbolo = this.atual;
                this.avancar();
                if (this.simboloAtual() === '=') {
                    this.adicionarSimbolo(delegua_1.default.MAIS_IGUAL, '+=');
                    this.avancar();
                }
                else if (this.simboloAtual() === '+') {
                    this.adicionarSimbolo(delegua_1.default.INCREMENTAR, '++');
                    this.avancar();
                }
                else {
                    this.adicionarSimbolo(delegua_1.default.ADICAO);
                }
                break;
            case ':':
                this.adicionarSimbolo(delegua_1.default.DOIS_PONTOS);
                this.avancar();
                break;
            case '%':
                this.inicioSimbolo = this.atual;
                this.avancar();
                switch (this.simboloAtual()) {
                    case '=':
                        this.avancar();
                        this.adicionarSimbolo(delegua_1.default.MODULO_IGUAL, '%=');
                        break;
                    default:
                        this.adicionarSimbolo(delegua_1.default.MODULO);
                        break;
                }
                break;
            case '*':
                this.inicioSimbolo = this.atual;
                this.avancar();
                switch (this.simboloAtual()) {
                    case '*':
                        this.avancar();
                        this.adicionarSimbolo(delegua_1.default.EXPONENCIACAO, '**');
                        break;
                    case '=':
                        this.avancar();
                        this.adicionarSimbolo(delegua_1.default.MULTIPLICACAO_IGUAL, '*=');
                        break;
                    default:
                        this.adicionarSimbolo(delegua_1.default.MULTIPLICACAO);
                        break;
                }
                break;
            case '!':
                this.avancar();
                if (this.simboloAtual() === '=') {
                    this.adicionarSimbolo(delegua_1.default.DIFERENTE, '!=');
                    this.avancar();
                }
                else {
                    this.adicionarSimbolo(delegua_1.default.NEGACAO);
                }
                break;
            case '=':
                this.avancar();
                if (this.simboloAtual() === '=') {
                    this.adicionarSimbolo(delegua_1.default.IGUAL_IGUAL, '==');
                    this.avancar();
                }
                else {
                    this.adicionarSimbolo(delegua_1.default.IGUAL);
                }
                break;
            case '&':
                this.adicionarSimbolo(delegua_1.default.BIT_AND);
                this.avancar();
                break;
            case '~':
                this.adicionarSimbolo(delegua_1.default.BIT_NOT);
                this.avancar();
                break;
            case '|':
                this.avancar();
                if (this.simboloAtual() === '|') {
                    this.adicionarSimbolo(delegua_1.default.EXPRESSAO_REGULAR, '||');
                    this.avancar();
                }
                else {
                    this.adicionarSimbolo(delegua_1.default.BIT_OR);
                }
                break;
            case '^':
                this.adicionarSimbolo(delegua_1.default.BIT_XOR);
                this.avancar();
                break;
            case '<':
                this.avancar();
                if (this.simboloAtual() === '=') {
                    this.adicionarSimbolo(delegua_1.default.MENOR_IGUAL, '<=');
                    this.avancar();
                }
                else if (this.simboloAtual() === '<') {
                    this.adicionarSimbolo(delegua_1.default.MENOR_MENOR, '<<');
                    this.avancar();
                }
                else if (this.simboloAtual() === '-') {
                    this.adicionarSimbolo(delegua_1.default.SETA_ESQUERDA, '<-');
                    this.avancar();
                }
                else {
                    this.adicionarSimbolo(delegua_1.default.MENOR);
                }
                break;
            case '>':
                this.avancar();
                if (this.simboloAtual() === '=') {
                    this.adicionarSimbolo(delegua_1.default.MAIOR_IGUAL, '>=');
                    this.avancar();
                }
                else if (this.simboloAtual() === '>') {
                    this.adicionarSimbolo(delegua_1.default.MAIOR_MAIOR, '>>');
                    this.avancar();
                }
                else {
                    this.adicionarSimbolo(delegua_1.default.MAIOR);
                }
                break;
            case '/':
                this.avancar();
                switch (this.simboloAtual()) {
                    case '/':
                        this.comentarioUmaLinha();
                        break;
                    case '*':
                        this.comentarioMultilinha();
                        break;
                    case '=':
                        this.adicionarSimbolo(delegua_1.default.DIVISAO_IGUAL, '/=');
                        this.avancar();
                        break;
                    default:
                        this.adicionarSimbolo(delegua_1.default.DIVISAO);
                        break;
                }
                break;
            case '\\':
                this.inicioSimbolo = this.atual;
                this.avancar();
                switch (this.simboloAtual()) {
                    case '=':
                        this.adicionarSimbolo(delegua_1.default.DIVISAO_INTEIRA_IGUAL, '\\=');
                        this.avancar();
                        break;
                    default:
                        this.adicionarSimbolo(delegua_1.default.DIVISAO_INTEIRA);
                        break;
                }
                break;
            case '?':
                this.avancar();
                if (this.simboloAtual() === ':') {
                    this.adicionarSimbolo(delegua_1.default.ELVIS, '?:');
                    this.avancar();
                }
                else {
                    this.adicionarSimbolo(delegua_1.default.INTERROGACAO);
                }
                break;
            // Esta sessão ignora espaços em branco (ou similares) na tokenização.
            case ' ':
            case '\0':
            case '\r':
            case '\t':
                this.avancar();
                break;
            // Ponto-e-vírgula é opcional em Delégua, mas em alguns casos pode ser
            // necessário. Por exemplo, declaração de `para` sem inicializador.
            case ';':
                this.adicionarSimbolo(delegua_1.default.PONTO_E_VIRGULA);
                this.avancar();
                break;
            case '"':
                this.analisarTexto('"');
                break;
            case "'":
                this.analisarTexto("'");
                break;
            default:
                if (this.eDigito(caractere))
                    this.analisarNumero();
                else if (this.eAlfabeto(caractere))
                    this.identificarPalavraChave();
                else {
                    this.erros.push({
                        linha: this.linha + 1,
                        caractere: caractere,
                        mensagem: 'Caractere inesperado.',
                    });
                    this.avancar();
                }
        }
    }
    mapear(codigo, hashArquivo) {
        const inicioMapeamento = (0, browser_process_hrtime_1.default)();
        this.erros = [];
        this.simbolos = [];
        this.inicioSimbolo = 0;
        this.atual = 0;
        this.linha = 0;
        this.codigo = codigo || [''];
        if (codigo.length === 0) {
            this.codigo = [''];
        }
        this.hashArquivo = hashArquivo;
        for (let iterador = 0; iterador < this.codigo.length; iterador++) {
            this.codigo[iterador] += '\0';
        }
        while (!this.eFinalDoCodigo()) {
            this.inicioSimbolo = this.atual;
            this.analisarToken();
        }
        if (this.performance) {
            const deltaMapeamento = (0, browser_process_hrtime_1.default)(inicioMapeamento);
            // eslint-disable-next-line no-undef
            console.log(`[Lexador] Tempo para mapeamento: ${deltaMapeamento[0] * 1e9 + deltaMapeamento[1]}ns`);
        }
        return {
            simbolos: this.simbolos,
            erros: this.erros,
        };
    }
}
exports.Lexador = Lexador;
//# sourceMappingURL=lexador.js.map