13 - Ejemplo práctico 4: parser infijo → postfijo

13.1 Objetivo

Transformar expresiones infijas en postfijas usando una pila de operadores y luego evaluar el resultado con una pila de operandos.

13.2 Características

  • Admite operadores +, -, *, / y paréntesis.
  • Separación en tokens por espacios.
  • Dos fases: conversión y evaluación.

13.3 Conversión infijo → postfijo

Se sigue el algoritmo de Shunting-yard:

  1. Recorrer cada token.
  2. Si es operando, enviarlo a la salida.
  3. Si es operador, compararlo con el tope de la pila y desapilar operadores con mayor o igual precedencia.
  4. Manejar paréntesis apilando “(” y desapilando hasta encontrarlo cuando aparece “)”.

13.4 Evaluación postfija

  • Recorrer los tokens postfijos.
  • Apilar operandos.
  • Ante un operador, desapilar los dos últimos, aplicar la operación y apilar el resultado.
  • El último valor en la pila es el resultado final.

13.5 Implementación en C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define MAX 256

typedef struct {
  char datos[MAX][32];
  int top;
} StackStr;

typedef struct {
  double datos[MAX];
  int top;
} StackNum;

void push_str(StackStr *s, const char *token) {
  strcpy(s->datos[s->top++], token);
}

char *peek_str(StackStr *s) {
  return s->top == 0 ? NULL : s->datos[s->top - 1];
}

char *pop_str(StackStr *s) {
  return s->top == 0 ? NULL : s->datos[--s->top];
}

void push_num(StackNum *s, double val) {
  s->datos[s->top++] = val;
}

double pop_num(StackNum *s) {
  return s->top == 0 ? 0 : s->datos[--s->top];
}

int es_op(const char *t) {
  return strcmp(t, "+") == 0 || strcmp(t, "-") == 0 ||
         strcmp(t, "*") == 0 || strcmp(t, "/") == 0;
}

int prec(const char *t) {
  if (strcmp(t, "+") == 0 || strcmp(t, "-") == 0) return 1;
  if (strcmp(t, "*") == 0 || strcmp(t, "/") == 0) return 2;
  return 0;
}

void infijo_a_postfijo(const char *entrada, char salida[][32], int *sal_len) {
  StackStr ops = {.top = 0};
  char buffer[32];
  const char *ptr = entrada;
  *sal_len = 0;

  while (sscanf(ptr, "%31s", buffer) == 1) {
    ptr = strstr(ptr, buffer) + strlen(buffer);
    if (isdigit(buffer[0])) {
      strcpy(salida[(*sal_len)++], buffer);
    } else if (strcmp(buffer, "(") == 0) {
      push_str(&ops, buffer);
    } else if (strcmp(buffer, ")") == 0) {
      while (ops.top && strcmp(peek_str(&ops), "(") != 0) {
        strcpy(salida[(*sal_len)++], pop_str(&ops));
      }
      pop_str(&ops);
    } else if (es_op(buffer)) {
      while (ops.top && prec(peek_str(&ops)) >= prec(buffer)) {
        strcpy(salida[(*sal_len)++], pop_str(&ops));
      }
      push_str(&ops, buffer);
    }
  }
  while (ops.top) {
    strcpy(salida[(*sal_len)++], pop_str(&ops));
  }
}

double evaluar_postfija(char tokens[][32], int len) {
  StackNum valores = {.top = 0};
  for (int i = 0; i < len; ++i) {
    if (!es_op(tokens[i])) {
      push_num(&valores, atof(tokens[i]));
    } else {
      double b = pop_num(&valores);
      double a = pop_num(&valores);
      if (strcmp(tokens[i], "+") == 0) push_num(&valores, a + b);
      else if (strcmp(tokens[i], "-") == 0) push_num(&valores, a - b);
      else if (strcmp(tokens[i], "*") == 0) push_num(&valores, a * b);
      else if (strcmp(tokens[i], "/") == 0) push_num(&valores, a / b);
    }
  }
  return pop_num(&valores);
}

int main(void) {
  const char *expr = "3 + 4 * 2 / ( 1 - 5 )";
  char postfija[MAX][32];
  int len = 0;
  infijo_a_postfijo(expr, postfija, &len);
  printf("Postfija: ");
  for (int i = 0; i < len; ++i) printf("%s ", postfija[i]);
  printf("\nResultado: %.2f\n", evaluar_postfija(postfija, len));
  return 0;
}

Con este ejemplo se cubre una aplicación clásica de pilas en compiladores y calculadoras, reforzando el dominio de la estructura.