UNPKG

@designliquido/delegua

Version:

Linguagem de programação simples e moderna usando português estruturado.

673 lines 25.7 kB
"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.codigo = []; 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); } eHexDigito(caractere) { return ((caractere >= '0' && caractere <= '9') || (caractere >= 'a' && caractere <= 'f') || (caractere >= 'A' && caractere <= 'F')); } eBinarioDigito(caractere) { return caractere === '0' || caractere === '1'; } eOctalDigito(caractere) { return caractere >= '0' && caractere <= '7'; } 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); const lexema = literal || texto; const comprimentoLexema = typeof lexema === 'string' ? lexema.length : 0; const comprimento = Math.max(comprimentoLexema, texto.length) || 1; const colunaInicio = this.inicioSimbolo + 1; const colunaFim = this.inicioSimbolo + comprimento; this.simbolos.push(new simbolo_1.Simbolo(tipo, lexema, literal, this.linha + 1, this.hashArquivo, colunaInicio, colunaFim)); } 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; } } } /** * Lê um comentário documentário (iniciado com `/**`), agregando o conteúdo * em um único token DOCUMENTARIO. Linhas com `*` inicial (convenção JSDoc) * têm o asterisco removido. */ comentarioDocumentario() { // Cursor está no primeiro '*' de '/**'. Avança para pular o segundo '*'. this.avancar(); let conteudo = ''; while (!this.eFinalDoCodigo()) { this.avancar(); if (this.simboloAtual() === '*' && this.proximoSimbolo() === '/') { // Fecha o documentário sem adicionar o '*' ao conteúdo. this.avancar(); // pula '*' this.avancar(); // pula '/' break; } conteudo += this.codigo[this.linha].charAt(this.atual); } // Divide por '\0' (separador de linha), remove asteriscos iniciais e filtra vazios. const conteudoLimpo = conteudo .split('\0') .map((l) => { const trimmed = l.trim(); return trimmed.startsWith('*') ? trimmed.substring(1).trim() : trimmed; }) .filter((l) => l.length > 0) .join('\n'); this.adicionarSimbolo(delegua_1.default.DOCUMENTARIO, conteudoLimpo || ''); } 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 valor = ''; this.avancar(); while (!this.eFinalDoCodigo()) { const caractere = this.simboloAtual(); if (caractere === delimitador) { this.avancar(); this.adicionarSimbolo(delegua_1.default.TEXTO, valor); const ultimoSimbolo = this.simbolos[this.simbolos.length - 1]; ultimoSimbolo.delimitadorTexto = delimitador; 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') { valor += '\n'; this.avancar(); continue; } if (caractere === '\\') { this.avancar(); const proximoCaractere = this.simboloAtual(); switch (proximoCaractere) { case 'n': valor += '\n'; break; case 't': valor += '\t'; break; case 'r': valor += '\r'; break; case 'b': valor += '\b'; break; case "'": valor += "'"; break; case '"': valor += '"'; break; case '\\': valor += '\\'; break; case 'e': valor += '\x1B'; break; case 'x': { let hex = ''; for (let i = 0; i < 2; i++) { const c = this.proximoSimbolo(); if (/[0-9a-fA-F]/.test(c)) { this.avancar(); hex += c; } else { break; } } valor += hex.length === 2 ? String.fromCharCode(parseInt(hex, 16)) : '\\x' + hex; break; } case '\0': break; // barra invertida no fim de linha: ignora e continua na próxima linha default: valor += '\\' + proximoCaractere; break; } } else { valor += caractere; } this.avancar(); } this.erros.push({ linha: this.linha + 1, caractere: this.simboloAnterior(), mensagem: 'Texto não finalizado.', }); } analisarHexadecimal() { this.avancar(); // Pula '0' this.avancar(); // Pula 'x' ou 'X' while (this.eHexDigito(this.simboloAtual())) { this.avancar(); } const hexString = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual); try { const bigintValue = BigInt(hexString); this.adicionarSimbolo(delegua_1.default.NUMERO, bigintValue); } catch (e) { this.erros.push({ linha: this.linha + 1, caractere: this.simboloAnterior(), mensagem: `Literal hexadecimal inválido: ${hexString}`, }); } } analisarBinario() { this.avancar(); // Pula '0' this.avancar(); // Pula 'b' ou 'B' while (this.eBinarioDigito(this.simboloAtual())) { this.avancar(); } const binaryString = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual); try { const bigintValue = BigInt(binaryString); this.adicionarSimbolo(delegua_1.default.NUMERO, bigintValue); } catch (e) { this.erros.push({ linha: this.linha + 1, caractere: this.simboloAnterior(), mensagem: `Literal binário inválido: ${binaryString}`, }); } } analisarOctal() { this.avancar(); // Pula '0' this.avancar(); // Pula 'o' ou 'O' while (this.eOctalDigito(this.simboloAtual())) { this.avancar(); } const octalString = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual); try { const bigintValue = BigInt(octalString); this.adicionarSimbolo(delegua_1.default.NUMERO, bigintValue); } catch (e) { this.erros.push({ linha: this.linha + 1, caractere: this.simboloAnterior(), mensagem: `Literal octal inválido: ${octalString}`, }); } } analisarNumero() { // Verifica se é um literal especial (hexadecimal, binário ou octal) if (this.simboloAtual() === '0') { const proximoChar = this.proximoSimbolo(); if (proximoChar === 'x' || proximoChar === 'X') { this.analisarHexadecimal(); return; } else if (proximoChar === 'b' || proximoChar === 'B') { this.analisarBinario(); return; } else if (proximoChar === 'o' || proximoChar === 'O') { this.analisarOctal(); return; } } // Análise de número decimal normal 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.inicioSimbolo = this.atual; this.avancar(); if (this.simboloAtual() === '.') { this.avancar(); if (this.simboloAtual() !== '.') { this.erros.push({ linha: this.linha + 1, caractere: this.simboloAtual(), mensagem: 'Esperado ou apenas um ponto, ou três pontos em sequência.', }); this.adicionarSimbolo(delegua_1.default.PONTO, '.'); } else { this.avancar(); this.adicionarSimbolo(delegua_1.default.RETICENCIAS, '...'); } } else { this.adicionarSimbolo(delegua_1.default.PONTO, '.'); } 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.CIRCUMFLEXO); 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 '*': if (this.proximoSimbolo() === '*') { this.comentarioDocumentario(); } else { 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