0Pricing
C Academy · Lección

Analizar expresiones

Construya un árbol sintáctico

Analizar expresiones es una lección gratuita de C Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C Academy incluye 4 lecciones en total.

De los tokens al árbol

El análisis sintáctico convierte un flujo plano de tokens en un árbol de sintaxis abstracta (AST) estructurado. El árbol codifica la precedencia y la agrupación que los tokens sin procesar solo sugieren.

Para 3 + 4 * 2, el AST anida la multiplicación bajo la suma, por lo que el resultado es 11, no 14.

Estructura de un nodo AST

Cada nodo es una hoja numérica o una operación binaria con dos hijos. Una estructura etiquetada con una unión mantiene reducido el uso de memoria.

El carácter del operador distingue entre +, -, * y / durante la ejecución.

typedef struct Node {
  enum { N_NUM, N_BINOP } kind;
  union {
    int value;                 /* N_NUM */
    struct {                   /* N_BINOP */
      char op;
      struct Node *left, *right;
    } bin;
  };
} Node;

Asignación de nodos

Dos constructores pequeños asignan nodos en el heap. Construir el árbol de abajo arriba significa crear primero las hojas y después envolverlas en nodos de operador.

En un intérprete de producción, registraría estas asignaciones para liberarlas más adelante.

#include <stdlib.h>

static Node *num(int v) {
  Node *n = malloc(sizeof *n);
  n->kind = N_NUM; n->value = v;
  return n;
}

static Node *binop(char op, Node *l, Node *r) {
  Node *n = malloc(sizeof *n);
  n->kind = N_BINOP;
  n->bin.op = op; n->bin.left = l; n->bin.right = r;
  return n;
}

La gramática

Usamos una gramática clásica basada en precedencia. expr gestiona + y -, term gestiona * y /, y factor gestiona los números y los paréntesis.

Como las reglas de mayor precedencia están más profundamente anidadas, la multiplicación se vincula automáticamente con más fuerza que la suma.

/* Grammar (EBNF):
   expr   = term   { ('+' | '-') term } ;
   term   = factor { ('*' | '/') factor } ;
   factor = NUMBER | '(' expr ')' ; */

Coincidencia de tokens

Un auxiliar expect consume un token del tipo requerido o aborta. Es el contrato del parser con el lexer.

Reutilizamos los auxiliares de anticipación cur y bump de la lección sobre el tokenizer.

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

static void expect(TokKind k) {
  if (cur().kind != k) {
    fprintf(stderr, "parse error: unexpected token\n");
    exit(1);
  }
  bump();
}

Análisis de un factor

Un factor es el átomo de la gramática: un número literal o una subexpresión entre paréntesis. Los paréntesis vuelven a llamar a parse_expr.

Esta recursión es la que da su nombre a los parsers de descenso recursivo.

static Node *parse_expr(void);

static Node *parse_factor(void) {
  if (cur().kind == TOK_NUM) {
    int v = cur().value; bump();
    return num(v);
  }
  expect(TOK_LPAREN);
  Node *e = parse_expr();
  expect(TOK_RPAREN);
  return e;
}

Análisis de un término

Un término analiza un factor y después repite el proceso mientras encuentra * o /, incorporando cada operador a un nodo binop asociativo por la izquierda.

La asociatividad por la izquierda significa que 8 / 4 / 2 se analiza como (8 / 4) / 2 = 1.

static Node *parse_term(void) {
  Node *left = parse_factor();
  while (cur().kind == TOK_STAR || cur().kind == TOK_SLASH) {
    char op = (cur().kind == TOK_STAR) ? '*' : '/';
    bump();
    left = binop(op, left, parse_factor());
  }
  return left;
}

Análisis de una expresión

La regla superior refleja parse_term, pero gestiona + y -. Cada nivel llama a la regla de precedencia inmediatamente superior, por lo que el árbol queda anidado correctamente.

Esta estructura de tres funciones es el núcleo del parser.

static Node *parse_expr(void) {
  Node *left = parse_term();
  while (cur().kind == TOK_PLUS || cur().kind == TOK_MINUS) {
    char op = (cur().kind == TOK_PLUS) ? '+' : '-';
    bump();
    left = binop(op, left, parse_term());
  }
  return left;
}

Inspección del árbol

Este programa analiza una expresión y la vuelve a imprimir con todos los paréntesis, mostrando cómo se resolvió la precedencia.

El formateador recorre recursivamente la misma estructura de nodos que construyó el parser.

#include <stdio.h>

typedef struct Node {
  int is_num; int value;
  char op; struct Node *l, *r;
} Node;

static Node *N(int v){ Node*n=calloc(1,sizeof*n); n->is_num=1; n->value=v; return n; }
static Node *B(char o,Node*a,Node*b){ Node*n=calloc(1,sizeof*n); n->op=o; n->l=a; n->r=b; return n; }

static void show(Node *n){
  if (n->is_num){ printf("%d", n->value); return; }
  printf("("); show(n->l); printf(" %c ", n->op); show(n->r); printf(")");
}

int main(void){
  /* 3 + 4 * 2  ->  (3 + (4 * 2)) */
  Node *ast = B('+', N(3), B('*', N(4), N(2)));
  show(ast); printf("\n");
  return 0;
}

Cómo evitar la recursión por la izquierda

Una gramática ingenua como expr = expr '+' term haría que parse_expr se llamara a sí misma indefinidamente. El descenso recursivo no puede gestionar la recursión por la izquierda directa.

Reescribir la regla como un bucle while sobre { '+' term } evita por completo la recursión infinita.

¿Por qué un AST?

El AST separa la sintaxis de la ejecución. El mismo árbol se puede evaluar, optimizar o compilar a bytecode sin volver a analizarlo.

A continuación recorreremos este árbol para calcular su valor.

Comprobación rápida

Considere cómo las capas de la gramática imponen la precedencia.

Repaso

Ha escrito un parser de descenso recursivo: estructuras de nodos del AST, constructores y las funciones expr/term/factor que codifican la precedencia y la asociatividad por la izquierda.

El árbol resultante está listo para evaluarse.

Preguntas frecuentes

¿La lección «Analizar expresiones» es gratis?

Sí — el texto completo de «Analizar expresiones» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C Academy, actualiza a CoddyKit PRO. El curso de C Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Analizar expresiones»?

Construya un árbol sintáctico Practicas C Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar C Academy?

No se requiere experiencia previa. C Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Analizar expresiones»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de C Academy?

Sí. Cada lección de C Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Tokenizar la entrada
  2. Analizar expresiones
  3. Evaluar el árbol
  4. Añadir variables
← Volver a C Academy