segunda-feira, 25 de setembro de 2017

Construindo um PARSER em javascript - VIII - Código completo

<html>
<head>
<title>Parse By Priority</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<script type="text/javascript">
// Routines
// Obtém o valor armazenado em uma variável
function varValue(nomeVar){
return VARIABLES[nomeVar];
}
// Obtém a variável à esquerda DE UMA POSIÇÃO (no caso um operador aritmético)
function leftOperand(pos){
return textoConv.substr(pos-3,3);
}
// Obtém a variável à direita DE UMA POSIÇÃO (no caso um operador aritmético)
function rightOperand(pos){
return textoConv.substr(pos+1,3);
}
// Obtém o conteúdo À ESQUERDA de uma string
function left(texto,n){
return texto.substr(0,n);
}
// Obtém o conteúdo À DIREITA de uma string (NÃO USADO)
function right0(texto,n){
return texto.slice(-n);
}
// Obtém o conteúdo À DIREITA DE UMA POSIÇÃO de uma string
function right1(texto,n){
return texto.substr(texto.length-n,n);
}
// Obtém o conteúdo À DIREITA DE UMA POSIÇÃO de uma string
function right(texto,n){
return texto.slice(-(texto.length-n));
}
// Ajusta um nome de variável num campo de número precedido de zeros
function adjustZero(texto){
texto = right1("000"+texto,2);
return "v"+texto
}
// Verifica se o caracter recolhido é um operador aritmético ou modificador de prioridade ()
function isOperator(Op){
var achou = (1==0);
var posicao = findOperator(Op,"+-/*^()");
if( posicao >= 0 ){
achou = (1==1);
}
return achou;
}
// Acha um tipo de operador em um texto ou expressão
function findOperator(op,texto){
return texto.indexOf(op);
}
// Acha uma operação de EXPONENCIAÇÃO em uma expressão
function findExponential(texto){
var posicao = findOperator("^", texto);
return posicao;
}
// Acha uma operação de MULTIPLICAÇÃO em uma expressão
function findMultiplication(texto){
var posicao = findOperator("*", texto);
return posicao;
}
// Acha uma operação de DIVISÃO em uma expressão
function findDivision(texto){
var posicao = findOperator("/", texto);
return posicao;
}
// Acha uma operação de SOMA em uma expressão
function findSum(texto){
var posicao = findOperator("+", texto);
return posicao;
}
// Acha uma operação de SUBTRAÇÃO em uma expressão
function findSub(texto){
var posicao = findOperator("-", texto);
return posicao;
}
// Acha um FECHA PARENTESES
function findCloseBrace(texto){
var posicao = findOperator(")", texto);
return posicao;
}
// Acha um ABRE PARENTESES
function findOpenBrace(posicao,texto){
for(i=posicao;i>=0;i--){
if( texto[i] == "(" ){
posicao = i;
break;
}
}
return posicao;
}
// Faz o teste de finalização e atualiza a Expressão aritmética
function updExpression(textoConv, posExp){
if( left(textoConv,posExp) == left(right(textoConv,posExp+4),left(textoConv,posExp).length) ) {
textoConv  = left(textoConv,posExp);
} else {
textoConv  = left(textoConv,posExp)+right(textoConv,posExp+4);
}
return textoConv;
}
// Faz o parse de valores, transformando-os em nomes de variáveis e vai armazenando os valores nestas variáveis
function parseVariables(texto){
var tamTexto = texto.length;

var carctr = "";
var ind = 0;
var proxCarctr = "";
var S = "";
var Variable;
var SINAL, PROX_SINAL;
for(i=0;i<tamTexto;i++){
carctr = texto[i];
SINAL = "";
if( isOperator(carctr) ){
// Obtem próximo caracter
proxCarctr = texto[i+1];
if( isOperator(proxCarctr) ){
SINAL = proxCarctr;
i++;
} else {
SINAL = "";
if( PROX_SINAL == "-" ){
SINAL = PROX_SINAL;
PROX_SINAL = "";
}
}
// Verifica se o operador do caracter atual é "-" (subtração)
if( carctr == "-" ){
PROX_SINAL = carctr;
}
ind++;
// Lida com o sinal
S = SINAL.concat(S);
// Alimenta o array VARIABLES
VARIABLES[ind] = parseFloat(S);
Variable = adjustZero(ind.toString());
VARIABLES[Variable] = parseFloat(S);
// Refaz a expressão
if( carctr == "-" ) { carctr = "+"; }
textoConv = textoConv + Variable + carctr;
S = "";
// Númeral, e não operador
} else {
S = S + carctr;
}
}
// Último sinal armazenado em PROX_SINAL
if( PROX_SINAL == "-" ){
SINAL = PROX_SINAL;
PROX_SINAL = "";
}
S = SINAL.concat(S);
ind++;
VARIABLES[ind] = parseFloat(S);
Variable = adjustZero(ind.toString());
VARIABLES[Variable] = parseFloat(S);
textoConv = textoConv + Variable;
S = "";
return tamTexto;
}
function equate(Expressao){
// É NECESSÁRIO EXECUTAR parseVariables
console.log("Tamanho: "+ parseVariables(Expressao));
var posExp = 0;
var aindaTem;
// Operador de expoente
OPERATOR = "Exp";
aindaTem = (0==0);
while ( aindaTem ){
//posExp = findExponential(textoConv);
posExp = FUNCOES[OPERATOR](textoConv);
if( posExp >= 0 ){
console.log("Exp:("+textoConv+")"+posExp);
// Divide operandos à esquerda e à direita do operador
var OpDir = rightOperand(posExp);
var OpEsq = leftOperand(posExp);
// Concretiza a operação
Resultado = Math.pow(varValue(OpEsq),varValue(OpDir));
console.log("Esq.:"+ OpEsq + "(" + varValue(OpEsq) + ") : " + OpDir + "(" + varValue(OpDir) + ") = " + Resultado);
// Substitui o valor da variável à esquerda
VARIABLES[OpEsq] = Resultado;
// Zera a variável à direita
VARIABLES[OpDir] = 0;
// testa o fim e refaz a expressão
textoConv = updExpression(textoConv, posExp);
} else {
aindaTem = (0==1);
}
}
console.log(textoConv);
// Operador de multiplicação
OPERATOR = "Mul";
aindaTem = (0==0);
while ( aindaTem ){
//posExp = findMultiplication(textoConv);
posExp = FUNCOES[OPERATOR](textoConv);
if( posExp >= 0 ){
console.log("Mul:("+textoConv+")"+posExp);
// Divide operandos à esquerda e à direita do operador
var OpDir = rightOperand(posExp);
var OpEsq = leftOperand(posExp);
// Concretiza a operação
Resultado = varValue(OpEsq)*varValue(OpDir);
console.log("Esq.:"+ OpEsq + "(" + varValue(OpEsq) + ") : " + OpDir + "(" + varValue(OpDir) + ") = " + Resultado);
// Substitui o valor da variável à esquerda
VARIABLES[OpEsq] = Resultado;
// Zera a variável à direita
VARIABLES[OpDir] = 0;
// testa o fim e refaz a expressão
textoConv = updExpression(textoConv, posExp);
} else {
aindaTem = (0==1);
}
}
console.log(textoConv);
// Operador de divisão
OPERATOR = "Div";
aindaTem = (0==0);
while ( aindaTem ){
//posExp = findDivision(textoConv);
posExp = FUNCOES[OPERATOR](textoConv);
if( posExp >= 0 ){
console.log("Div:("+textoConv+")"+posExp);
// Divide operandos à esquerda e à direita do operador
var OpDir = rightOperand(posExp);
var OpEsq = leftOperand(posExp);
// Concretiza a operação
Resultado = varValue(OpEsq)/varValue(OpDir);
console.log("Esq.:"+ OpEsq + "(" + varValue(OpEsq) + ") : " + OpDir + "(" + varValue(OpDir) + ") = " + Resultado);
// Substitui o valor da variável à esquerda
VARIABLES[OpEsq] = Resultado;
// Zera a variável à direita
VARIABLES[OpDir] = 0;
// testa o fim e refaz a expressão
textoConv = updExpression(textoConv, posExp);
} else {
aindaTem = (0==1);
}
}
console.log(textoConv);
// Operador de subtração

// Operador de soma
OPERATOR = "Sum";
aindaTem = (0==0);
while ( aindaTem ){
//posExp = findSum(textoConv);
posExp = FUNCOES[OPERATOR](textoConv);
if( posExp >= 0 ){
console.log("Sum:("+textoConv+") - Pos: "+posExp);
// Divide operandos à esquerda e à direita do operador
var OpDir = rightOperand(posExp);
var OpEsq = leftOperand(posExp);
// Concretiza a operação
Resultado = varValue(OpEsq)+varValue(OpDir);
console.log("Esq.:"+ OpEsq + "(" + varValue(OpEsq) + ") : " + OpDir + "(" + varValue(OpDir) + ") = " + Resultado);
// Substitui o valor da variável à esquerda
VARIABLES[OpEsq] = Resultado;
// Zera a variável à direita
VARIABLES[OpDir] = 0;
// testa o fim e refaz a expressão
textoConv = updExpression(textoConv, posExp);
} else {
aindaTem = (0==1);
}
}
return textoConv;
}


function solvExpression(Expressao){
// Variáveis iniciais

//var ind = 0;
var Resultado = 0;
var OPERATOR;
var posExp_ant = 999999;
var posIni = 0;
var posFim = 0;

var ExpressaoTemp = "";
var Tam = 0;
var Temp;
var LadoEsq,LadoDir;
// Operador de parênteses
OPERATOR = ")";
aindaTem = (0==0);
while ( aindaTem ){
posExp = findCloseBrace(Expressao);
if( posExp >= 0 ){
VARIABLES = [];
textoConv = "";
ind = 0;
posFim = findCloseBrace(Expressao);
posIni = findOpenBrace(posFim,Expressao);
ExpressaoTemp = Expressao.substr(posIni+1,posFim-posIni-1);
Temp = equate(ExpressaoTemp);
LadoEsq = left(Expressao,posIni);
LadoDir = Expressao.substr(posFim+1,9999);
Expressao =  LadoEsq +VARIABLES["v01"]+ LadoDir;
} else {
aindaTem = (0==1);
}
}
VARIABLES = [];
textoConv = "";
ind = 0;
Temp = equate(Expressao);
return VARIABLES["v01"];
}
//// Program
/*
solvExpression                        Fornece o resultado de uma expressão aritmética
           |
+---------  equate          Resolve a expressão, operação por operação
|
+-------- parseVariables Faz o parse da expressão aritmética
1 - Coloca cada número em um item do array VARIABLES
2 - Coloca sinais negativos dentro dos respectivos itens do array VARIABLES
*/

// Constantes e ARRAYS do CORE do parser e solve
var VARIABLES = [];
var FUNCOES = [];
FUNCOES["Exp"] = findExponential;
FUNCOES["Mul"] = findMultiplication;
FUNCOES["Div"] = findDivision;
FUNCOES["Sum"] = findSum;
FUNCOES["Sub"] = findSub;
FUNCOES["Abp"] = findOpenBrace;
FUNCOES["Fbp"] = findCloseBrace;
var textoConv = "";
// Processando a expressão
console.log("======================================================");
var result = solvExpression(Expressao);
console.log("Resultado: " + result );
</script>
</head>
<body>
</body>
</html>

Construindo um PARSER em javascript - VII - Código

Neste post apresentaremos e explicaremos a função mestra de nosso Parser, a função solvExpression.

Objetivo da função

O objetivo de solvExpression é scanear a expressão aritmética, com níveis de parênteses, achando, a cada passada, o parênteses mais interno e resolvendo a expressão aritmética no interior dele. Este procedimento será repetido até o fim das operações aritméticas disponíveis.

Variáveis locais necessárias

Uma série de variáveis de apoio são necessárias para o processamento dos níveis de parênteses, por esta função:

function solvExpression(Expressao){
// Variáveis iniciais
var Resultado = 0;
var OPERATOR;
var posExp_ant = 999999;
var posIni = 0;
var posFim = 0;
var ExpressaoTemp = "";
var Tam = 0;
var Temp;
var LadoEsq,LadoDir;
// Operador de parênteses
OPERATOR = ")";
aindaTem = (0==0);

Resultado - Nossa conhecida variável que armazena os resultados das operações aos pares de operandos;
OPERATOR - Armazena o operador aritmético da vez, ou parênteses;
posExp_ant - Armazena a posição onde o operador anterior está. Começa com um valor bem alto;
posIni - Posição inicial na expressão de um fragmento;
posFim - Posição final na expressão de um fragmento;
ExpressãoTemp - armazena a expressão temporária para análise;
Tam - Tamanho da Expressão;
Temp - Variável temporária;
LadoEsq e LadoDir - Fragmentos à esquerda e à direita de uma expressão;
aindaTem - Flag booleana que armazena a condição de continuidade de algum processo;

Loop operacional

A busca por níveis de parênteses é contínua, até que se esgote por completo:

while ( aindaTem ){
posExp = findCloseBrace(Expressao);
if( posExp >= 0 ){
VARIABLES = [];
textoConv = "";
ind = 0;
posFim = findCloseBrace(Expressao);
posIni = findOpenBrace(posFim,Expressao);
ExpressaoTemp = Expressao.substr(posIni+1,posFim-posIni-1);
Temp = equate(ExpressaoTemp);
LadoEsq = left(Expressao,posIni);
LadoDir = Expressao.substr(posFim+1,9999);
Expressao =  LadoEsq +VARIABLES["v01"]+ LadoDir;
} else {
aindaTem = (0==1);
}
}

Em nossas explicações, dissemos que os níveis de parênteses são facilmente identificados pelo operador ")" (fecha parênteses), e não pelo abre-parênteses, como os obsessivos do Parse à esquerda poderiam esperar. Por esta razão, a primeira função a ser chamada neste loop é findCloseBrace:

// Acha um FECHA PARENTESES
function findCloseBrace(texto){
var posicao = findOperator(")", texto);
return posicao;
}

Esta função chama a função mais básica findOperator, que utiliza o método indexOf do objeto string para achar o fecha-parênteses.

Procedimentos pós-loop

Ao fim do loop de parênteses, é necessário tomar algumas providências, pois resta uma expressão sem níveis de parênteses a interpretar:

VARIABLES = [];
textoConv = "";
ind = 0;
Temp = equate(Expressao);
return VARIABLES["v01"];

:A função equate fará a última avaliação de expressão, e o resultado final será devolvido no item cujo índice é "v01".

Conclusão

O último nível aqui apresentado do Parser é o primeiro nível em ordem de execução, que chama os demais.

Neste post apresentamos o código completo.


Construindo um PARSER em javascript - VI - Código

Neste post vamos fornecer o código da função equate, que resolve a expressão aritmética já transformada em variáveis pela função parseVariables, mostrada no post anterior.

function equate(Expressao){
// É NECESSÁRIO EXECUTAR parseVariables
var posExp = 0;
var aindaTem;
// Operador de expoente
OPERATOR = "Exp";
aindaTem = (0==0);
while ( aindaTem ){
posExp = FUNCOES[OPERATOR](textoConv);
if( posExp >= 0 ){
// Divide operandos à esquerda e à direita do operador
var OpDir = rightOperand(posExp);
var OpEsq = leftOperand(posExp);
// Concretiza a operação
Resultado = Math.pow(varValue(OpEsq),varValue(OpDir));
// Substitui o valor da variável à esquerda
VARIABLES[OpEsq] = Resultado;
// Zera a variável à direita
VARIABLES[OpDir] = 0;
// testa o fim e refaz a expressão
textoConv = updExpression(textoConv, posExp);
} else {
aindaTem = (0==1);
}
}
// Operador de multiplicação
OPERATOR = "Mul";
aindaTem = (0==0);
while ( aindaTem ){
posExp = FUNCOES[OPERATOR](textoConv);
if( posExp >= 0 ){
// Divide operandos à esquerda e à direita do operador
var OpDir = rightOperand(posExp);
var OpEsq = leftOperand(posExp);
// Concretiza a operação
Resultado = varValue(OpEsq)*varValue(OpDir);
// Substitui o valor da variável à esquerda
VARIABLES[OpEsq] = Resultado;
// Zera a variável à direita
VARIABLES[OpDir] = 0;
// testa o fim e refaz a expressão
textoConv = updExpression(textoConv, posExp);
} else {
aindaTem = (0==1);
}
}
// Operador de divisão
OPERATOR = "Div";
aindaTem = (0==0);
while ( aindaTem ){
posExp = FUNCOES[OPERATOR](textoConv);
if( posExp >= 0 ){
// Divide operandos à esquerda e à direita do operador
var OpDir = rightOperand(posExp);
var OpEsq = leftOperand(posExp);
// Concretiza a operação
Resultado = varValue(OpEsq)/varValue(OpDir);
// Substitui o valor da variável à esquerda
VARIABLES[OpEsq] = Resultado;
// Zera a variável à direita
VARIABLES[OpDir] = 0;
// testa o fim e refaz a expressão
textoConv = updExpression(textoConv, posExp);
} else {
aindaTem = (0==1);
}
}
// Operador de soma
OPERATOR = "Sum";
aindaTem = (0==0);
while ( aindaTem ){
posExp = FUNCOES[OPERATOR](textoConv);
if( posExp >= 0 ){
// Divide operandos à esquerda e à direita do operador
var OpDir = rightOperand(posExp);
var OpEsq = leftOperand(posExp);
// Concretiza a operação
Resultado = varValue(OpEsq)+varValue(OpDir);
// Substitui o valor da variável à esquerda
VARIABLES[OpEsq] = Resultado;
// Zera a variável à direita
VARIABLES[OpDir] = 0;
// testa o fim e refaz a expressão
textoConv = updExpression(textoConv, posExp);
} else {
aindaTem = (0==1);
}
}
return textoConv;
}

Esta função tem quatro partes. cada uma trata de um operador ("^", "*", "/" e "+"). E como já ressaltamos nos posts anteriores, a subtração foi transformada em soma, pois nosso parseVariables já providenciou a colocação dos sinais negativos como parte do valor do operando, dentro do array VARIABLES.

Vamos explicar a lógica de um operador, para explicar a resolução das expressões, pois ela valerá para todos. Seja o trecho que executa a multiplicação.

Iniciamos um loop while com a condição aindaTem em verdadeiro. Então chamamos a função redirecionada pelo array FUNCOES, índice "Mul" (Multiplicação). O array FUNCOES direciona esta função para findMultiplication:

// Acha uma operação de MULTIPLICAÇÃO em uma expressão
function findMultiplication(texto){
var posicao = findOperator("*", texto);
return posicao;
}

Esta função chama a função findOperator com o argumento "*" (operador de multiplicação):

// Acha um tipo de operador em um texto ou expressão
function findOperator(op,texto){
return texto.indexOf(op);
}

Que, por sua vez, utiliza a função indexOf do objeto string para achar a posição de um caracter, no caso o operador "*". Se este operador for achado, ocorrerá o tratamento da operação de multiplicação entre os dois operandos adjacentes, expressos por variáveis "Vnn".

Extração dos operandos

Uma vez achado um operador, o programa obtém o operando à sua esquerda (leftOperand) e à sua direita (rightOperand). Vamos mostrar os códigos destas funções:

// Obtém a variável à esquerda DE UMA POSIÇÃO (no caso um operador aritmético)
function leftOperand(pos){
return textoConv.substr(pos-3,3);
}
// Obtém a variável à direita DE UMA POSIÇÃO (no caso um operador aritmético)
function rightOperand(pos){
return textoConv.substr(pos+1,3);
}

Ambas não tem muito mistério. Apenas utilizam o tão conhecido método substr do objeto string.

No entanto, estes são apenas os nomes dos índices das variáveis, é preciso obter o valor de cada um para calcular o Resultado:

Resultado varValue(OpEsq)*varValue(OpDir);

Vejamos como funciona varValue:

// Obtém o valor armazenado em uma variável
function varValue(nomeVar){
return VARIABLES[nomeVar];
}

Ela se refere, através de um índice, ao array VARIABLES, e extrai seu valor. Exemplo:

VARIABLES["V01"] fornece o valor da variável V01, estabelecida pela função parseVariables.

A variável textoConv contém a última transformação da expressão em termos de variáveis, e não mais em termos de numerais.

Tratamento do Resultado

Uma vez obtido o Resultado, a variável do array indexada por OpEsq recebe este resultado, e variável do array indexada por OpDir recebe o valor 0 (zero).

Atualização da expressão

Uma vez obtido o resultado e armazenado no array de variaveis, é preciso refazer a expressão na forma de de variáveis em textoConv. Isto é feito pela função updExpression, cujo código apresentamos aqui:

// Faz o teste de finalização e atualiza a Expressão aritmética
function updExpression(textoConv, posExp){
if( left(textoConv,posExp) == left(right(textoConv,posExp+4),left(textoConv,posExp).length) ) {
textoConv  = left(textoConv,posExp);
} else {
textoConv  = left(textoConv,posExp)+right(textoConv,posExp+4);
}
return textoConv;
}

É feito um teste para verificar se o processamento da expressão, na forma de variáveis, após o ajuste da expressão, depois de uma operação aritmética. Este teste, em suma, averigua se a expressão colhida à esquerda é igual ao fragmento de mesmo comprimento na expressão da direita. Se isto for verdade, textoConv (a próxima expressão a ser interpretada) será apenas o conteúdo do lado esquerdo, senão haverá a concatenação do lado esquerdo com o direito, suprimindo o operador da última operação e o operando da direita (pois ele foi zerado, e o resultado da operação colocado no operando da esquerda).

Quando a interpretação de toda a expressão estiver terminada, a variável de índice "V01" estará armazenando o resultado final do nível corrente de parênteses..

Conclusão

 Nesta fase, as operações aritméticas são efetuadas, e temos o resultado final de uma expressão pura ou extraída do interior de um nível de parênteses.

No próximo post apresentaremos os detalhes da função solvExpression, que trata os níveis de parênteses.













sexta-feira, 22 de setembro de 2017

Construindo um PARSER em javascript - V - Código

Agora que já conhecemos a teoria, vamos colocar a mão na massa, fornecendo, passo a passo, os códigos que possibilitam a construção do Parser. O leitor poderá notar que não utilizamos, de forma nenhuma, a programação a objetos, de forma a tornar a compreensão a mais clara possível, e acessível aos programadores iniciantes.

Inicializações e chamada com ênfase no Parser

Vamos mostrar o início do programa, porém chamando apenas o Parser, para que as fases posteriores possam ser absorvidas com ampla compreensão desta fase fundamental:

// Constantes e ARRAYS do CORE do parser e solve
var VARIABLES = [];
var FUNCOES = [];
FUNCOES["Exp"] = findExponential;
FUNCOES["Mul"] = findMultiplication;
FUNCOES["Div"] = findDivision;
FUNCOES["Sum"] = findSum;
FUNCOES["Sub"] = findSub;
FUNCOES["Abp"] = findOpenBrace;
FUNCOES["Fbp"] = findCloseBrace;
var textoConv = "";
// Processando a expressão
var res0 = parseVariables("17/2+19/3+1.01^10");

O array VARIABLES armazena as variáveis da expressão parseada, em itens cujos nomes são a junção de "V" com um número sequencial ajustado em campo de 2 numerais (V01, V02, ...), conforme mostrado nos esquemas dos posts anteriores.

O array FUNCOES armazena as funções que acham os operadores. E por que fazer assim, ao invés de utilizar os nomes das funções ? Para futuras automatizações e reduções de código.

O Parse

Nesta primeira abordagem, separamos uma chamada do Parse, para analisá-lo, antes de abordar o processo global de análise de prioridades, parse e resolução da expressão:

// Processando a expressão
var res0 = parseVariables("17/2+19/3+1.01^10");

A observação digna de nota é a de que utilizamos o operador "^" para a exponenciação. No entanto, na resolução, utilizaremos a função Math.pow(), pois, como você poderá constatar, a expressão com operador dá um erro absurdo. experimente testar no debug de um navegador para comprovar este fato.

O código

Chamamos a função Parser de parseVariables, de forma que este código possa ser agregado a outros parsers de instruções, comandos e configurações sem risco de colisão de nomes, em seus programas futuros:

function parseVariables(texto){
var tamTexto = texto.length;
var carctr = "";
var ind = 0;
var proxCarctr = "";
var S = "";
var Variable;
var SINAL, PROX_SINAL;
for(i=0;i<tamTexto;i++){
carctr = texto[i];
SINAL = "";
if( isOperator(carctr) ){
// Obtem próximo caracter
proxCarctr = texto[i+1];
if( isOperator(proxCarctr) ){
SINAL = proxCarctr;
i++;
} else {
SINAL = "";
if( PROX_SINAL == "-" ){
SINAL = PROX_SINAL;
PROX_SINAL = "";
}
}
// Verifica se o operador do caracter atual é "-" (subtração)
if( carctr == "-" ){
PROX_SINAL = carctr;
}
ind++;
// Lida com o sinal
S = SINAL.concat(S);
// Alimenta o array VARIABLES
VARIABLES[ind] = parseFloat(S);
Variable = adjustZero(ind.toString());
VARIABLES[Variable] = parseFloat(S);
// Refaz a expressão
if( carctr == "-" ) { carctr = "+"; }
textoConv = textoConv + Variable + carctr;
S = "";
// Númeral, e não operador
} else {
S = S + carctr;
}
}
// Último sinal armazenado em PROX_SINAL
if( PROX_SINAL == "-" ){
SINAL = PROX_SINAL;
PROX_SINAL = "";
}
S = SINAL.concat(S);
ind++;
VARIABLES[ind] = parseFloat(S);
Variable = adjustZero(ind.toString());
VARIABLES[Variable] = parseFloat(S);
textoConv = textoConv + Variable;
S = "";
return tamTexto;
}

Os fatores importantes a serem levados em conta para entender esta função são os seguintes:


  1. A expressão trabalhada aqui não contém parênteses. A cada loop da função solvExpression, obtemos uma expressão no nível mais interno dos parênteses restantes do loop anterior;
  2. Os caracteres numéricos são acumulados da esquerda para a direita, até que se encontre um operador aritmético;
  3. Encontrado um operador aritmético, é preciso verificar se ele está junto a um outro, fato que ocorre quando o próximo operando tiver um sinal negativo. Isto se dá nas seguintes combinações: "*-", "/-" e "^-". Se isto ocorrer, o sinal negativo será armazenado na variável SINAL, pois ele se refere ao próximo operando, e não a este. Então, o sinal é transferido para a variável PROX_SINAL. Na próxima passada, este sinal será lido desta variável;
  4. O sinal negativo é armazenado dentro do item, e o operador aritmético "-" é substituído pelo de soma ("+");
  5. A variável ind é incrementada a cada operador lido, pois sua presença indica que a leitura dos caracteres de um operando foi concluída;
  6. Os nomes dos índices das variáveis no array VARIABLES é a concatenação da letra "V" (de variável) com o conteúdo da variável ind ajustado em um campo de 2 zeros. Tal ajuste é feito pela função adjustZero.

Para compreender esta função, o diagrama do primeiro post desta série é muito útil. Para facilitar, vamos reproduzí-lo aqui:


Função adjustZero


// Ajusta um nome de variável num campo de número precedido de zeros
function adjustZero(texto){
texto = right1("000"+texto,2);
return "v"+texto
}
Esta função utiliza um truque muito simples. Coloca-se os zeros à esquerda do número, e depois coleta-se a string obtida a partir da direita. O ajuste é automético. Tal coleta pela direita é feita pela função right1.

Função right1


// Obtém o conteúdo À DIREITA DE UMA POSIÇÃO de uma string
function right1(texto,n){
return texto.substr(texto.length-n,n);
}

Como o javascript não disponibiliza a função right explícita, esta operação é feita pela função substr.

Conclusão

Resolver uma expressão é bem mais eficiente se representarmos seus operandos de uma forma estruturada, armazenada em array, com nomes de comprimentos iguais, para serem analisados pela próxima etapa de forma previsível.

Veja no próximo post o código para resolução das expressões aritméticas.











quinta-feira, 21 de setembro de 2017

Construindo um PARSER em javascript - IV

Até o momento, nesta série de artigos sobre a construção de um Parser para expressões, não nos preocupamos com a definição forçada de prioridades através do uso de parênteses.

Neste post, vamos mostrar como será resolvido o problema dos níveis de parênteses, de forma extremamente simples, seguindo a mesma lógica da resolução das expressões mostrada anteriormente, vamos repetir, sem as árvores de operandos e operadores comumente utilizadas.

Fluxograma de rastreio dos parênteses

Os parênteses serão encarados da mesma forma que os operadores aritméticos. O truque é achar não um abre-parênteses, e sim um fecha parênteses, retrocedendo o pointer de caracteres até o primeiro abre-parênteses que o precede:



Repare que sempre que procurarmos o primeiro fecha-parênteses, ele será o mais interno, tornando o nosso algoritmo o mais eficiente na busca pelas expressões de maior prioridade para resolução.

No próximo post mostraremos o código javascript do parse da expressão sem níveis de parênteses.

Conclusão

Existem formas mais eficientes de se tratar os níveis de parênteses nas expressões aritméticas.



Construindo um PARSER em javascript - III

Já vimos, nesta série sobre a construção de um Parser de expressões, a colocação dos valores em variáveis e o esquema da resolução da expressão.

Neste post, vamos mostrar o fluxograma da resolução da equação dentro de um nível, ou seja, considerando o conteúdo englobado por um nível de parênteses.

Fluxograma do equate

O princípio do equate é achar, de acordo com as regras do nosso segundo artigo da série, as operações em ordem decrescente de prioridades, e ir resolvendo asm mesmas de duas em duas, sem nenhuma árvore binária, sem ramos e nem folhas.

Como a expressão não apresenta o operador de exponenciação, iniciamos pelo operador de multiplicação.



Por este Fluxograma de processos e dados fica claro o uso dos próprios recursos de memória onde ficam alocados os operandos e operadores ao invés de se utilizar a técnica de colocação dos mesmos em árvores binárias, para o tradicional trajeto de cima para baixo e da esquerda para a direita.

Os próprios dados mínimos para a resolução da expressão servem, do jeito que estão, ao nosso objetivo. O processo é bem simples. Acha-se o operador da prioridade da hora, resolve-se o par e monta-se a expressão resultante, repetindo o processo até o fim das operações de multiplicação. Depois utiliza-se a mesma lógica para o operador de divisão, seguido da mesma lógica para o operador de soma.

O mesmo diagrama pode ser repetido para os operadores de divisão e soma.

No próximo post mostraremos como lidar com os níveis de parênteses.

Conclusão

A lógica utilizada, favorecida pelo parser que dividiu convenientemente os operandos e operadores, normalizando os sinais negativos, é a da resolução por pares, da maior prioridade até a menor prioridade.


Construindo um PARSER em javascript - II

No primeiro post  desta série tomamos conhecimento da lógica do Parser de expressões, sem nos preocupar em solucionar a expressão lida. Neste post faremos o equate, ou seja, vamos rastrear a expressão transformada e armazenada na variável textoConv e resolver cada par de operações para obter o resultado final.

Diagrama teórico

A ordem de resolução de uma expressão, ainda sem levar em conta os parênteses, obedece a um conjunto de prioridades, mostrado a seguir:


Nossas explicações vão ser dadas em torno do exemplo fornecido no primeiro post (90*2-80*3).

Procedimento teórico

Os passos para o procedimento equate resolver a expressão estão esquematizados abaixo:


Início

O equate procura o operador de exponenciação "^".
Não acha.

Passo 1

O equate procura o operador de multiplicação "*" e acha.
O equate utiliza o operador como referencial e extrai o lado esquerdo (V03) e o lado direito (V04).
O equate efetua a multiplicação e armazena o resultado no lado esquerdo (V03).
O equate zera o lado direito (V04).
Agora a expressão resultante é V01*V02+V03.

Passo 2

O equate procura o operador de multiplicação "*" e acha.
O equate utiliza o operador como referencial e extrai o lado esquerdo (V01) e o lado direito (V02).
O equate efetua a multiplicação e armazena o resultado no lado esquerdo (V01).
O equate zera o lado direito (V02).
Agora a expressão resultante é V01+V03.

Passo 3

O equate procura o operador de adição "+" e acha.
O equate utiliza o operador como referencial e extrai o lado esquerdo (V01) e o lado direito (V03).
O equate efetua a adição e armazena o resultado no lado esquerdo (V01).
Agora a expressão resultante é V01.

O Resultado final é o conteúdo da variável de array V01.

No próximo post explicaremos o equate (resolução da expressão).

Conclusão

Neste tipo de procedimento de resolução de expressões não temos as ineficientes e complexas etapas de colocação da equação em árvores hierárquicas. As próprias estruturas que armazenam as variáveis vão sendo resumidas, até que reste apenas o elemento que atende pelo índice V01, onde fica o resultado final.

O rastreamento dos ramos das árvores binárias de expressões é algo mais elegível para o ladop da elegância do que da praticidade.





Construindo um PARSER em javascript - I

Entender a filosofia e a construção de um PARSER de expressões aritméticas é muito importante para aqueles que pretendem fazer programas parametrizados ou com algumas macros ou scripts de configuração e até execução.

Expressão aritmética

As expressões aritméticas são compostas de operandos (números) e de operadores ("+", "-", "*", "/" e exponencial ["^"]).

O sinal dos operandos

Um dos dificultadoreS, que iremos constatar no momento da programação de um Parser, é que os operandos podem ter o sinal negativo. Isto poderá fazer com que dois operadores ("*-" ou "/-" ou "^-") apareçam juntos na expressão, quebrando a lógica natural esperada de OPERANDO-OPERADOR. Em nosso Parser, vamos armazenar os operandos conjuntamente com os seus sinais, e considerar que, na última prioridade de execução das operações, SÓ EXISTEM SOMAS.

A teoria em um diagrama

Percorrer expressões aritméticas é um procedimento cujo esquema geral está representado no diagrama a seguir:


Vamos percorrer a expressão aritmética geral mostrada em (1), caracter a caracter, discernindo, a cada um que é lido, se é um numeral, sinal ou operador aritmético (op1, op2, op3, ...), até alcançarmos o fim da expressão. Na primeira passada, o sinal do operando estará expresso como um operador. E como dissemos, quando encontrarmos o operador da subtração ("-"), o que faremos é colocá-lo como sinal do operando internamente em uma variável de memória, conjuntamente com o operando, e transformar o operador em operador de soma. Esta filosofia de interpretação da expressão aritmética vai economizar um operador, e impedirá a colisão de dois operadores, como ressaltamos mais acima ("O sinal dos operandos").

A prática em um diagrama exemplo

A seguir mostramos um exemplo prático do que falamos no item anterior, expresso em um diagrama:


Aqui estamos tratando das expressões seguindo a ordem de prioridade. Em outro post vamos tratar dos níveis de parênteses, assunto que vai fechar esta série.

Algumas diretrizes para um PARSER:

  1. O rastreamento é feito da esquerda para a direita;
  2. O rastreamento é feito caracter a caracter;
  3. Os espaços entre os caracteres devem ser evitados e, caso existam, suprimidos;
  4. A cada OPERADOR detectado, uma nova variável é reservada em um array;
  5. O operador da subtração é convertida em sinal do número que o sucede;

A razão da diretriz 5 se explica pelo fato citado no item "O sinal dos operandos".

Fluxograma do Parser para expressões

Para deixar o procedimento bem explicado, vamos apresentar um fluxograma misto de instruções e dados:



EXPRESSAO - Variável do tipo string que armazena toda a expressão.
SINAL - Sinal do operando, levando em consideração a regra 5 citada.
[i] - Caracter sendo lido (indice i).
[i+1] - Caracter seguinte ao lido (índice i+1).
PROX_SINAL - Armazena o sinal do próximo operando, pois o sinal precede um operando.
ind - Indice para o array de variáveis que armazenam os operandos.
S - Variável que armazena os caracteres, um a um, rastreados de um operando. Quando é achado um operador, esta variável é esvaziada.

Resultado em debug

Executando o código (listagem completa no último post desta série) em javascript, obtemos o seguinte resultado no navegador:


O array exibido mostra os operandos tanto em itens sequenciais quanto em itens indexados pelos nomes das variáveis ("V01", "V02", ...).

Conclusão

O PARSE de expressões é uma operação simples, mas que exige a observância de sinais e operadores.



terça-feira, 19 de setembro de 2017

substring ou slice em javascript

O Javascript apresenta um novo comando para retirada de pedaços de uma string, em alternativa ao substr, em alguns casos. É o comando slice.

A seguir, apresentamos o uso de ambos e os resultados obtidos:


O substr tem a grande vantagem de extrair um pedaço da string a partir de um ponto de referência. Já o slice negativo faz apenas o papel de um comando right, disponível em muitas linguagens.