Skip to content
xXaceitunaXxPublic

About

Implementación de un intérprete interactivo tipo REPL del lenguaje de consultas sobre hojas de cálculo cell. Trabajo final de la asignatura de Gramáticas y Lenguajes Formales (GLF) del curso 2025-2026.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

title Práctica 3 GLF
author Juan Horrillo Crespo

Introducción y objetivos

El objetivo de esta práctica es implementar un pequeño intérprete de fórmulas inspirado en una hoja de cálculo. El programa carga primero un fichero con valores asociados a celdas y después entra en modo interactivo, donde acepta fórmulas introducidas por el usuario, las analiza y devuelve su resultado.

El lenguaje soporta referencias a celdas, números, operadores aritméticos y relacionales, rangos, llamadas a función y expresiones condicionales mediante IF. Para procesarlo se han utilizado Flex para el análisis léxico y Bison para el análisis sintáctico. A partir de la gramática se construye un árbol de sintaxis abstracta (AST), que permite separar claramente la comprobación semántica de la evaluación final.

Los objetivos principales son:

  • Definir formalmente el lenguaje aceptado por el intérprete.
  • Implementar el análisis léxico y sintáctico de las fórmulas.
  • Construir un AST que represente la estructura de cada expresión.
  • Validar errores léxicos, sintácticos y semánticos de forma controlada.
  • Evaluar referencias, operaciones, rangos, funciones y condicionales.
  • Documentar las decisiones de diseño, las limitaciones y los casos de prueba.

Compilación y ejecución

El proyecto se compila con make. Es necesario tener instalados gcc, make, flex, bison y la biblioteca de desarrollo de readline.

make

El ejecutable generado se llama cell. Para iniciarlo hay que pasar como argumento un fichero de datos .cdf:

./cell data/hoja_1.cdf

Una vez dentro del intérprete, las fórmulas deben comenzar por =. También se puede consultar la ayuda de una función con ? y salir con EXIT.

>>> =A1+A2*2
[1] 32.000000
>>> ?SUM
>>> EXIT

Para eliminar los artefactos generados por Flex/Bison en el directorio build y el ejecutable cell:

make clean

Definición formal del lenguaje

En esta sección del documento se definirá formalmente el lenguaje de operaciones sobre hojas de calculo descrito en el enunciado como una gramática independiente de contexto.

Alfabeto

El alfabeto está formado por los subalfabetos

  • Letras mayúsculas: $\Sigma_L={A, B, C, D, \dots, Y, Z}$
  • Dígitos decimales: $\Sigma_Z={0, 1, 2, 3, \dots, 8, 9}$
  • Operadores aritméticos: $\Sigma_A={+,-,*,/}$
  • Operadores relacionales: $\Sigma_R={<,>,=,\leq,\geq,<>}$
  • Paréntesis: $\Sigma_P={;(;, ;);}$
  • Separadores: $\Sigma_S={;.;,;\text{``,''};,;:;}$
  • Interpretación de sentencia: $\Sigma_I={=, ?}$

Por tanto

$$ \Sigma=\Sigma_L\cup\Sigma_Z\cup\Sigma_A\cup\Sigma_R\cup\Sigma_P\cup\Sigma_S\cup\Sigma_I $$

Producciones

  1. Inicio de sentencia:

Con un = delante para indicar que se debe evaluar la sentencia, un ? para mostrar la ayuda de una función o EXIT para salir del programa. El comando EXIT se procesa directamente en el bucle interactivo, antes de llamar al analizador léxico y sintáctico.

S ==> =<Expr>|?<Id>
  1. Expresiones y recursividad:

Para poder definir correctamente operaciones complejas, modelizo mediante las produccciones la prioridad de operadores (relacionales, suma o resta y multiplicación o división en ese orden)

<Expr>   ==> <Expr><OpRel><Arit>|<Arit>
<Arit>   ==> <Arit><OpAdd><Term>|<Term>
<Factor> ==> <Factor><OpMul><Term>|<Term>
<Term>   ==> (<Expr>)|-<Term>|<Func>|<Rango>|<Celda>|<Valor>
<OpRel>  ==> <|>|=|<=|>=|<>
<OpAdd>  ==> +|-
<OpMul>  ==> *|/
  1. Función e identificadores:

Define de que elementos se compone una función y cuales son las funciones aceptadas (por su identificador)

<Func> ==> <Id>(<Arg>)
<Id>   ==> <Letra>+

La validez del identificador de función se comprueba durante el análisis semántico. Sintácticamente se aceptan identificadores en mayúsculas, y después se resuelven contra las funciones implementadas (SUM, AVERAGE, MIN, MAX, COUNT, PRODUCT, ABS, FLOOR, CEIL, ROUND, IF).

  1. Argumentos, rango, celda y otros:

Todo lo que pueda ser un argumento de una función

<Arg>     ==> <Expr>|<Expr>,<Arg>
<Rango>   ==> <Celda>:<Celda>
<Celda>   ==> <Columna><P>
<Columna> ==> <Letra>|<Letra><Columna>
<Letra>   ==> cualquier c en Letras Mayúsculas
  1. Valor:

Basicamente cualquier número real bien formado, es decir, sin ceros por la izquierda en la parte entera y sin punto si no hay parte decimal. El signo negativo no forma parte del número: se trata como operador unario en la gramática.

<Valor>    ==> <Numero>
<Numero>   ==> <Natural>|<Natural>.<Decimal>
<Natural>  ==> 0|<P>
<P>        ==> <Digito+><C>
<C>        ==> <Digito><C>|epsilon
<Digito+>  ==> 1|2|3|···|8|9
<Digito>   ==> 0|1|2|···|8|9

Muchas de estas producciones se pueden simplificar gracias al uso de expresiones regulares en el lexer, como la del número o las celdas. Se explicará en detalle en la especificación léxica del intérprete.

Gestión de fichero de datos

El fichero de datos que contiene los valores de las celdas será del formato presentado en el enunciado de la práctica, esto es un valor por línea y separado por espacios de la forma CELDA VALOR. El ejemplo que sue muestra es el siguiente:

A1 10
A2 20
A3 30
B1 4.6
B2 8
B3 12
C1 -2
C2 -5.15

equivalente a la hoja de cálculo

A B C
1 10 4.6 -2
2 20 8 -5.15
3 30 12

Tal y como dice el enunciado, es importante especificar la política adoptada frente a errores en el fichero:

  • Una línea mal formada es una línea que no sigue el formato CELDA VALOR, separada obligatoriamente por un único caracter espacio, o que contenga más de una entrada. Ejemplos de error: A212, A2, 12, A2,12, A2 12 A3 4.
  • La repetición de celdas será tratado como un error, es indispensable que solo aparezcan una vez en el fichero. Ejemplo de error:
A2 12
A2 20
  • También es un error introducir un VALOR no numérico en las entradas. Ejemplos de error: A1 HOLA, A2 TRUE.
  • Será un error de referencia si la CELDA no sigue los estandares (una columna dada por una o varias letras mayúsculas y un número positivo natural). Ejemplos de error: A 12, 12 12, A0 12, A-1 12.
  • Además, será necesario que en cada línea presente del fichero haya una entrada, no podrá haber líneas vacias o formadas por caracteres de escape \t o \n entre otros y obligatoriamente mínimo una entrada, no se admiten ficheros de datos vacios. Ejemplo error:
A1 1\n

A2 2\n
  • Para acotar el problema en cuanto a los tipos de datos, todo VALOR será punto flotante de doble precisión (double). No se admite notación cientifica ni expresiones aritméticas, funciones, etc. Ejemplos de error: A3 IF(A2 > A1, 1, 0), A1 1e10, A1 2+1.

En caso de que ocurra un error de datos, antes de comenzar la sesión interactiva se mostará un error específico al usuario y terminará el programa.

Los mensajes de error son los siguentes:

  • Error de malformación: Error de sintaxis: Se esperaban 2 tokens <CELDA VALOR>, se encontraron 3 (A2 12 4).
  • Error de colisión (repetición): Redefinición de celda: El identificador A2 ya fue inicializado.
  • Error de tipo: Tipo de dato inválido: El valor 'TRUE' no pertenece al dominio de los números reales.
  • Referencia invalida: Referencia a celda inválida: 'A0' no cumple el formato requerido.
  • Errores de fichero:
    • Fichero vacío: El archivo de entrada está vacío.
    • Línea vacía: Línea vacía o compuesta exclusivamente caracteres de escape/control

Todos los edge cases se expondrán en detalle en la sección de casos de prueba

Para almacenar los datos en memoría usaré la estructura hash table, que permite realizar accesos por clave (la celda). Usaré la implementación de las famosas bibliotecas single header stb (enlace github).

El fichero de datos tendrá la extensión .cdf (cell data file), pero cualquier documento de texto plano con el formato correcto podrá ser analizado.

Especificación Léxica

El analizador léxico se implementa en grammar/cell.l utilizando Flex. A continuación se describen los tokens definidos, las expresiones regulares empleadas y las decisiones de diseño más relevantes.

Tokens definidos

Token Lexema(s) Descripción
T_FORMULA = Signo = que introduce una fórmula o actúa como operador relacional de igualdad
T_HELP ? Consulta de ayuda de una función
T_CELL p.ej. A1, BC10, AA15 Referencia de celda; lleva el texto como valor semántico (yylval.str)
T_NUM p.ej. 0, 25, 3.14 Número entero o real (sin signo); lleva el valor double en yylval.num
T_OPR_SUM + Operador aritmético suma
T_OPR_RES - Operador aritmético resta / unario negativo
T_OPR_MUL * Operador aritmético multiplicación
T_OPR_DIV / Operador aritmético división
T_OPR_LESS < Operador relacional menor que
T_OPR_GREATER > Operador relacional mayor que
T_OPR_LEQ <= Operador relacional menor o igual
T_OPR_GEQ >= Operador relacional mayor o igual
T_OPR_NEQ <> Operador relacional distinto
T_LPAR ( Paréntesis de apertura
T_RPAR ) Paréntesis de cierre
T_COMMA , Separador de argumentos
T_COLON : Separador de rango
T_FUNCTION SUM, AVERAGE, IF Identificador de función; lleva el texto como valor semántico (yylval.str)

El lexema = tiene dos roles distintos. Al comienzo de la entrada introduce la fórmula (S ==> T_FORMULA EXPR); dentro de una expresión actúa como operador relacional de igualdad (OP_REL ==> T_FORMULA). Esta distinción se resuelve sintácticamente.

Expresiones regulares empleadas

LETRA      →  [A-Z]
DIGITO     →  [0-9]
DIGITO_NZ  →  [1-9]
CELDA      →  {LETRA}+{DIGITO_NZ}{DIGITO}*
NATURAL    →  0 | {DIGITO_NZ}{DIGITO}*
NUMERO     →  {NATURAL}(\.{DIGITO}+)?
BLANCO     →  [ \t]+

La expresión CELDA captura referencias del tipo A1, BC10, AA15. Es importante que la columna numérica no puede comenzar por cero (por eso se hace uso de DIGITO_NZ), lo que impide referencias inválidas como A0.

Distinción entre celdas y funciones

El lexer no define un token distinto para cada función. Cualquier secuencia de letras mayúsculas ({LETRA}+) se reconoce como T_FUNCTION, y la resolución concreta se hace después, durante el análisis semántico.

Las referencias de celda se reconocen antes que los identificadores de función:

  1. Referencia de celda válida ({CELDA})
  2. Referencia de celda mal formada ({LETRA}+{DIGITO}+)
  3. Número mal formado con ceros iniciales
  4. Número ({NUMERO})
  5. Identificador de función ({LETRA}+)
  6. Operadores y separadores

Por ejemplo, A1 se reconoce como celda, mientras que SUM se reconoce como identificador de función. Cuando se encuentra con un identificador no implementado también llega al parser como un token T_FUNCTION. Estos identificadores se rechazan más tarde con un error semántico.

Signo negativo

El signo - no se gestiona léxicamente como parte del número. El token T_NUM solo reconoce números sin signo. El signo negativo se trata sintácticamente como el operador unario T_OPR_RES aplicado a una expresión, lo que permite formas como -(A1+B1) o -SUM(A1:A3) sin ambigüedad.

Espacios en blanco y saltos de línea

Los blancos (espacio y tabulador) se ignoran. Como el sistema funciona como un intérprete interactivo (una entrada vacía no debería hacer nada) y por motivos de implementación (la función readline no incluye el salto de línea), he decidido que este carácter no forme parte del alfabeto.

Tratamiento de errores léxicos

Se definen tres tipos de errores léxicos: símbolos no reconocidos, referencias de celda mal formadas y números mal formados por ceros iniciales.

Los mensajes que devuelve el lexer son:

Caso Ejemplo Mensaje
Referencia de celda mal formada A0, A01 Error léxico: referencia de celda mal formada '<lexema>'
Número mal formado 01, 00.5 Error léxico: número mal formado '<lexema>'
Símbolo no reconocido @, #, letras minúsculas Error léxico: símbolo '<lexema>' no reconocido

Para todos los casos se pone a true la bandera error_lexico y se devuelve el token especial YYerror, de forma que después Bison no muestre además un error sintáctico genérico para la misma entrada (de otra forma imprimiría Error: syntax error).

Especificación sintática

El analizador sintáctico se implementa en grammar/cell.y utilizando Bison. Con el se comprueba que la entrada sigue la forma del lenguaje de fórmulas de hojas de cálculo propuesta y al mismo tiempo construye el árbol de sintaxis abstracta (AST) que se analizará y evaluará después de forma semántica.

Gramática utilizada

La gramática finalmente implementada en el parser es la siguiente:

S        -> T_FORMULA EXPR
          | T_HELP T_FUNCTION

EXPR     -> EXPR OP_REL ARIT
          | ARIT

ARIT     -> ARIT OP_ADD FACT
          | FACT

FACT     -> FACT OP_MUL TERM
          | TERM

TERM     -> T_LPAR EXPR T_RPAR
          | T_OPR_RES TERM
          | FUNC
          | RANGO
          | T_CELL
          | T_NUM

FUNC     -> T_FUNCTION T_LPAR ARG T_RPAR

ARG      -> ARG_ITEM
          | ARG T_COMMA ARG_ITEM

ARG_ITEM -> EXPR

RANGO    -> T_CELL T_COLON T_CELL

OP_REL   -> T_OPR_LESS
          | T_OPR_GREATER
          | T_OPR_LEQ
          | T_OPR_GEQ
          | T_OPR_NEQ
          | T_FORMULA

OP_ADD   -> T_OPR_SUM
          | T_OPR_RES

OP_MUL   -> T_OPR_MUL
          | T_OPR_DIV

Como ya se mencionó, el comando EXIT no aparece en la gramática porque se resuelve dentro del bucle interactivo principal antes de llamar a Bison. Así se evita mezclar "comandos del intérprete" con las fórmulas que debe reconocer el parser.

No terminales principales

  • S representa una entrada completa. Puede ser una fórmula introducida por = o una consulta de ayuda introducida por ?.
  • EXPR agrupa las expresiones relacionales. Aquí se reconocen comparaciones como A1>A2, A1=A2 o SUM(A1:A3)<>0.
  • ARIT agrupa sumas y restas.
  • FACT agrupa multiplicaciones y divisiones.
  • TERM representa las unidades básicas de una expresión: paréntesis, negación, llamada a función, rangos, celda o simplemente un número.
  • FUNC reconoce llamadas a función con una lista de argumentos entre paréntesis.
  • ARG y ARG_ITEM construyen listas de argumentos separadas por comas. Cada argumento puede ser cualquier expresión válida.
  • RANGO reconoce la forma sintáctica CELDA:CELDA.
  • OP_REL, OP_ADD y OP_MUL traducen tokens del lexer a operadores internos del AST.

Precedencia y asociatividad

Para resolver la precedencia no se hace con las directivas de Bison %left o %right, sino separando la gramática en varios niveles, que crean producciones correctas mediante recursividad:

  1. EXPR reconoce operadores relacionales.
  2. ARIT reconoce suma y resta.
  3. FACT reconoce multiplicación y división.
  4. TERM reconoce elementos básicos y expresiones entre paréntesis.

Con esta estructura, * y / tienen más prioridad que + y -, y las operaciones aritméticas tienen más prioridad que las comparaciones. Por ejemplo, =A1+A2*2 se interpreta como A1+(A2*2), mientras que =(A1+A2)*2 fuerza otra la otra agrupación por los paréntesis.

La recursión izquierda en las producciones ARIT -> ARIT OP_ADD FACT y FACT -> FACT OP_MUL TERM hace que los operadores sean asociativos por la izquierda. Así, A1-A2-A3 se interpreta como (A1-A2)-A3.

El operador - tiene dos usos: como resta binaria en OP_ADD y como negación en TERM -> T_OPR_RES TERM. Esta segunda producción permite expresiones como =-A1, =-(A1+A2) o =-SUM(A1:A3) que si hubiesemos tratado en la especificación léxica no hubieran sido posibles.

Construcción del AST

Si bien es cierto que usar un AST quizás se salga de los objetivos de la práctica y de la asignatura, buscando el cómo hacer análisis y evaluaciones semánticas me encontre con el blog de Ruslan Spivak, donde tenía una publicación sobre como construior un intérprete mediante un AST: Let's Build A Simple Interpreter, Part 7. Me pareció muy curioso y potente y decidí implementar el proyecto de la misma forma.

Durante el análisis sintáctico se construye un árbol de sintaxis abstracto. Cada producción de Bison devuelve un nodo o una lista de nodos que representan de forma abstracta la interpretación de la exprtesión. El resultado de una fórmula completa se guarda en la variable global ast_root para poder acceder a el posteriormente.

S -> T_FORMULA EXPR    { ast_root = EXPR; }

El AST se construye con los constructores definidos para cada tipo de nodo:

Producción Nodo construido
EXPR -> EXPR OP_REL ARIT new_binary_node(op, left, right)
ARIT -> ARIT OP_ADD FACT new_binary_node(op, left, right)
FACT -> FACT OP_MUL TERM new_binary_node(op, left, right)
TERM -> T_OPR_RES TERM new_unary_node(UNARY_NEG, child)
TERM -> FUNC Devuelve el nodo de función ya construido
TERM -> RANGO Devuelve el nodo de rango ya construido
TERM -> T_CELL new_cell_node(cell_reference)
TERM -> T_NUM new_value_node(value)
FUNC -> T_FUNCTION '(' ARG ')' new_function_node(name, arguments)
RANGO -> T_CELL ':' T_CELL new_range_node(start, end)

Las listas de argumentos se construyen con new_arguments() y add_argument(). Internamente son arrays dinámicos de punteros a nodos AST, lo que permite que una función reciba una mezcla de literales, celdas, subexpresiones, rangos y otras funciones.

Relación con el análisis semántico (o limitaciones sintácticas)

La gramática se limita a comprobar la forma sintáctica. Algunas restricciones se dejan deliberadamente para el análisis semántico:

  • T_FUNCTION acepta cualquier identificador formado por letras mayúsculas. Si la función existe o no se decide después.
  • IF no tiene una producción especial. Se reconoce como una función normal y luego se comprueba que tenga tres argumentos y que el primero sea una comparación.
  • RANGO acepta cualquier par CELDA:CELDA. Después se valida si el rango es horizontal o vertical, si está invertido, si es matricial o si contiene celdas sin valor.
  • Una celda sintácticamente correcta puede no existir en la tabla cargada; eso también se detecta semánticamente.

Esta separación evita convertir la gramática en una lista de casos especiales y mantiene el parser centrado en la estructura de la fórmula.

Tratamiento de errores sintácticos

Para mostrar errores personalizados dependientes del contexto, se utiliza la "directiva" de configuración de Bison %define parse.error custom, lo que permite implementar la función yyreport_syntax_error. En esta función se usan distintas macros de Bison que permiten conocer el estado en el que se está, que token llevo a ese estado, el token que se acaba de leer (lookahead) o los tokens esperados por la producción. Mediante una combinación de todo eso, me permite tratar los siguentes casos de error:

Caso Ejemplo Mensaje
Entrada sin símbolo inicial válido SUM(A1:A3) Error de sintaxis: Consulta desconocida, se esperaba '=' o '?'
Función sin paréntesis de apertura =SUM A1 Error de sintaxis: Se esperaba '(' tras la función
Dos expresiones contiguas =SUM(A1 A2) Error de sintaxis: Falta un operador entre expresiones
Coma sin expresión antes o después =SUM(,A1) Error de sintaxis: Coma mal situada, se esperaba una expresión antes o después de ','
Coma antes de cerrar argumentos =SUM(A1,) Error de sintaxis: Coma mal situada, no puede aparecer justo antes de ')'
Paréntesis desbalanceados =SUM(A1:A3 Error de sintaxis: Paréntesis desbalanceados, se esperaba ')'
Rango incompleto o mal formado =SUM(A1:) Error de sintaxis: Incorrecto formato de rango, se esperaba <CELDA:CELDA>
Expresión incompleta =A1+ Error de sintaxis: Expresión incompleta, se esperaba un número, una celda, una función o '('
Ayuda sin función ? Error de sintaxis: Se esperaba un identificador de función para mostrar la ayuda
Otro error no clasificado depende del contexto Error de sintaxis: Error de sintaxis no específico

Gestión de errores semánticos

Una vez que Bison ha aceptado la entrada, el programa ya no trabaja con texto sino con el AST construido durante el análisis sintáctico. Sobre ese árbol se hacen dos pasadas distintas:

  1. Análisis semántico, que comprueba que la fórmula tiene sentido con respecto a las reglas del lenguaje y a la tabla de celdas cargada.
  2. Evaluación, que calcula el valor final solo si el análisis semántico previo ha sido correcto.

Esta separación permite rechazar expresiones bien formadas sintácticamente pero inválidas en tiempo de ejecución, como =C1 (celda no definida), =ABS(A1:A3) o =SUM(A1:B2), antes de intentar calcular su resultado.

Errores semánticos contemplados

Se han contemplado los errores semánticos propuestos por el enunciado de la práctica y alguna extensión propuesta por mi.

  1. Referencia a una celda inexistente Cada nodo AST_CELL se valida consultando la tabla cargada desde el fichero de datos. Si una celda no existe o no tiene valor asignado, el análisis semántico falla. Esto afecta tanto a referencias directas (=C1) como a celdas incluidas dentro de un rango (=SUM(A1:A5)).

  2. División entre cero La división se comprueba durante la evaluación del nodo binario BINARY_DIV, porque el divisor puede ser una expresión completa y no necesariamente un literal y no se sabe su valor hasta tiempo de ejecución. Por ejemplo, =A1/0 se detecta como error, pero =A1/B3 también siempre que B3 sea 0.0.

  3. Aridad incorrecta en funciones Las funciones unarias (ABS, FLOOR, CEIL, ROUND) deben recibir exactamente un argumento. La función IF debe recibir exactamente tres argumentos y además con restricciones: una operación relacional como condición condición, una expresión si es verdadera y una expresión si es falsa. Las funciones SUM, AVERAGE, MIN, MAX, COUNT, PRODUCT aceptan un número variable de argumentos.

  4. Rango no válido Sintácticamente un rango es cualquier par CELDA:CELDA, pero semánticamente se comprueba que pueda expandirse de forma válida. En la implementación se admiten rangos horizontales y verticales, pero no rangos matriciales como A1:B2. También se rechazan rangos invertidos como A3:A1 y rangos que contienen alguna celda sin valor asignado.

  5. Uso de un rango donde no procede

    Los rangos se han restringido a argumentos de funciones. Por tanto, una expresión como =A1:A3 es sintácticamente reconocible, pero semánticamente inválida. Además, las funciones unarias no aceptan rangos como argumento, porque operan sobre un único valor escalar.

Análisis semántico sobre el AST

La función analyze_tree() recorre recursivamente en postorden el AST antes de evaluar la expresión. Para cada tipo de nodo se aplica una regla:

  • AST_VALUE: siempre es válido.
  • AST_CELL: debe existir en la tabla de datos.
  • AST_UNARY: se analiza su hijo.
  • AST_BINARY: se analizan sus dos operandos.
  • AST_FUNCTION: primero se comprueba que la función exista y luego se delega en analyze_function().
  • AST_RANGE: solo es válido cuando se analiza desde una función que acepta rangos.

La validación de funciones se separa por familias. Para IF se comprueba su aridad y que el primer argumento sea una comparación. Para las funciones unarias se comprueba que haya un único argumento y que no sea un rango (no se puede hacer el valor absoluto de un rango). Las funciones con aridad indefinida recorren todos sus argumentos y, cuando encuentran un rango, llaman a analyze_range() para verificarlo (en estas si que se puede incluir un rango, pues en la evaluación se expanden como otros argumentos más).

Evaluación

Si el análisis semántico devuelve éxito, el programa llama a evaluate_tree(). La evaluación también recorre el AST:

  • Los literales devuelven su valor double.
  • Las celdas consultan su valor en la tabla cargada.
  • El negativo unario cambia el signo del resultado de su hijo.
  • Los nodos binarios calculan suma, resta, multiplicación, división o comparaciones.
  • Las funciones se delegan a evaluate_function().

Las comparaciones devuelven un valor lógico que en realidad es un número (para simplificar la cantidad de tipos en el parser). En evaluate_if(), la condición decide qué rama se evalúa: si es verdadera se evalúa el segundo argumento; si es falsa se evalúa el tercero (aprovecha que en C cualquier valor no nulo se interpreta como verdadero).

Los rangos se expanden dentro de las funciones agregadas. Por ejemplo, SUM(A1:A3,10,B1) suma los valores de A1, A2, A3, el literal 10 y el valor de B1. COUNT cuenta cada celda del rango como un elemento independiente (osea, su "tamaño").

Mensajes de error semántico

Aquí se muestra otra vez la tabla con el error y el mensaje personalizado asociado, en este caso refiriendose a la semántica del lenguaje.

Caso Ejemplo Mensaje
Celda inexistente o sin valor =C1 Error semántico: La celda referenciada no tiene valor asignado
Función no definida =UNKNOWN(A1) Error semántico: 'UNKNOWN' no es una función definida
Rango usado fuera de una función =A1:A3 Error semántico: Los rangos solo se pueden usar como argumentos de funciones
División entre cero =A1/0 Error semántico: División entre cero
IF con aridad incorrecta =IF(A1>A2,100) Error semántico: La función 'IF' esperaba 3 argumentos
IF sin comparación =IF(A1,100,200) Error semántico: La función 'IF' esperaba una comparación
Función unaria con aridad incorrecta =ABS(A1,A2) Error semántico: La función 'ABS' esperaba un único argumento
Función unaria con rango =ABS(A1:A3) Error semántico: La función 'ABS' no acepta rangos como argumento
Rango matricial =SUM(A1:B2) Error semántico: No se admiten rangos matriciales
Rango invertido =SUM(A3:A1) Error semántico: Inconsistencia en el acceso, mal orden
Ayuda de función no definida ?UNKNOWN Error semántico: 'UNKNOWN' no es una función definida

Casos de prueba

Todas las pruebas toman como datos de referencia el fichero hoja_1.cdf, de forma que los valores esperados sean reproducibles. El contenido del fichero es el siguiente:

A1 12
A2 10
A3 5
A4 -2
B1 4
B2 4
B3 0
Z99 1000
ZZZZ1 2000

El objetivo de estos casos es cubrir las partes principales del lenguaje descritas en el enunciado:

Categoría Casos cubiertos
Operaciones simples Referencias a celdas, literales numéricos, suma, multiplicación, división y negativo unario.
Precedencia y paréntesis Comparación entre =A1+A2*2 y =(A1+A2)*2.
Operadores relacionales Comparaciones con >, <, = y <>.
Rangos Rangos verticales (A1:A3), horizontales (A1:B1) y rangos inválidos.
Funciones de aridad variable SUM, AVERAGE, MIN, MAX, COUNT y PRODUCT, mezclando rangos, celdas y literales.
Funciones unarias ABS, FLOOR, CEIL y ROUND.
Condicionales IF con condición verdadera, condición falsa y errores en la condición.
Errores sintácticos Entrada sin =, coma mal situada, falta de operador, paréntesis desbalanceados, rango incompleto y ayuda incompleta.
Errores semánticos División entre cero, celda inexistente, función no definida, aridad incorrecta, rango matricial, rango invertido y uso indebido de rangos.

Los errores léxicos se comprueban con entradas independientes porque no dependen del fichero de datos. Los casos representativos son referencias de celda mal formadas (=A0, =A01), números con ceros iniciales (=01, =00.5) y símbolos no reconocidos (=@, =#).

En la siguente tabla se muestra la batería de casos de prueba propuesta:

Expresión Valor esperado Prueba
=A1 12.000000 Referencia simple a celda existente.
=42 42.000000 Literal numérico.
=A1+A2*2 32.000000 Precedencia de multiplicación sobre suma.
=(A1+A2)*2 44.000000 Uso de paréntesis para alterar precedencia.
=-A4 2.000000 Operador unario negativo.
=A1/A2 1.200000 División correcta.
=A1/0 Error semántico: división entre cero Detección de división por cero.
=A1/B3 Error semántico: división entre cero Detección de división por cero.
=A1>A2 1.000000 Comparación mayor que verdadera.
=A1<A2 0.000000 Comparación menor que falsa.
=A1=A2 0.000000 Comparación de igualdad.
=A1<>A2 1.000000 Comparación de desigualdad.
=SUM(A1:A3) 27.000000 Suma de rango vertical.
=SUM(A1:A3,1) 28.000000 Función variádica mezclando rango y literal.
=AVERAGE(A1:A3) 9.000000 Media de rango usando tamaño real del rango.
=MIN(A1:A4,B1) -2.000000 Mínimo con rango y celda suelta.
=MAX(A1:A4,B1) 12.000000 Máximo con rango y celda suelta.
=COUNT(A1:A3,B1) 4.000000 Conteo de elementos expandiendo rangos.
=PRODUCT(A1:A3) 600.000000 Producto de todos los valores de un rango.
=ABS(A4) 2.000000 Función unaria ABS.
=FLOOR(3.9) 3.000000 Función unaria FLOOR.
=CEIL(3.1) 4.000000 Función unaria CEIL.
=ROUND(3.6) 4.000000 Función unaria ROUND.
=IF(A1>A2,100,200) 100.000000 IF con condición verdadera.
=IF(A1<A2,100,200) 200.000000 IF con condición falsa.
=SUM(A1:B1) 16.000000 Rango horizontal válido.
=Z99+ZZZZ1 3000.000000 Referencias con columnas largas.
=C1 Error semántico: celda sin valor asignado Referencia a celda inexistente.
=SUM(A1:B2) Error semántico: no se admiten rangos matriciales Rechazar rangos rectangulares.
=SUM(A3:A1) Error semántico: inconsistencia en el acceso Rechazar rangos invertidos.
=SUM(A1:A5) Error semántico: celda sin valor asignado Rango con alguna celda inexistente.
=ABS(A1:A3) Error semántico: función no acepta rangos Rechazar uso de rango en una función unaria.
=ABS(A1,A2) Error semántico: esperaba un único argumento Validación de aridad en funciones unarias.
=IF(A1,100,200) Error semántico: esperaba una comparación Validación de condición de IF.
=IF(A1>A2,100) Error semántico: IF esperaba 3 argumentos Validación de aridad de IF.
=UNKNOWN(A1) Error semántico: función no definida Rechaza funciones no definidas.
SUM(A1:A3) Error de sintaxis: se esperaba = o ? Consulta sin símbolo inicial.
=SUM(A1,) Error de sintaxis: coma mal situada Coma antes de cerrar argumentos.
=SUM(A1 A2) Error de sintaxis: falta operador Dos expresiones contiguas sin operador.
=SUM(A1:A3 Error de sintaxis: se esperaba ) Paréntesis desbalanceado.
=SUM(A1:) Error de sintaxis: formato de rango incorrecto Rango incompleto.
?SUM Muestra ayuda de SUM Consulta de ayuda válida.
? Error de sintaxis: se esperaba función Consulta de ayuda incompleta.

Limitaciones y mejoras

Limitaciones

Estas limitaciones coinciden con recomendación del enunciado (y algunas propuestas mías): acotar el lenguaje, simplificar el modelo de rangos y usar una estructura sencilla para consultar valores de celdas.

  • Solo se trabaja con valores numéricos. No hay cadenas, fechas, booleanos como tipo separado ni formato de celda. Las comparaciones devuelven un valor lógico suficiente para resolver IF, pero internamente todo se maneja como valores numéricos.
  • Los rangos están acotados a una fila o una columna. Se admiten rangos como A1:A5 o A1:C1, pero se rechazan rangos matriciales como A1:C3.
  • No hay dependencias entre fórmulas almacenadas. El fichero de datos carga valores fijos de celdas; no se guardan fórmulas dentro de celdas ni se recalculan dependencias.
  • El modo interactivo es simple. Lee una línea, procesa una fórmula o termina con EXIT. No implementa edición avanzada propia, sesiones persistentes ni comandos adicionales de gestión.
  • La validación de funciones es semántica. Sintácticamente cualquier identificador en mayúsculas puede ser una función; si no está implementada se rechaza después.
  • Los números aceptados son reales sencillos. No se admite notación científica ni formatos alternativos.
  • No se implementa recuperación de errores. Ante una entrada incorrecta se informa del error y se pasa a la siguiente línea del intérprete.

Mejoras añadidas

Además de los requisitos básicos, se han añadido algunas mejoras:

  • Construcción de un AST. Considero que el uso del AST es una mejora de arquitectura: permite separar claramente análisis sintáctico, análisis semántico y evaluación. No añade una función nueva al usuario, pero hace que el intérprete sea más mantenible y evita evaluar mediante sustituciones textuales o recorridos ad hoc.
  • Mensajes de error personalizados. Se personalizan errores léxicos, sintácticos y semánticos para que el usuario entienda mejor qué parte de la entrada es incorrecta.
  • Documentación de funciones con ?. Se añade una consulta de ayuda como ?SUM, que muestra documentación específica de la función.
  • Soporte de historial y lectura interactiva con readline. Aunque el enunciado solo exige un bucle interactivo simple, el uso de readline mejora la experiencia de uso permitiendo editar la línea actual o acceder al historial de expresiones con las flechas.

Posibles mejoras futuras

Como trabajo futuro se podrían añadir:

  • Rangos rectangulares completos (A1:C3).
  • Notación científica y más formatos numéricos.
  • Tipos adicionales, como texto o booleanos explícitos.
  • Más funciones compatibles con hojas de cálculo reales.

Anexo: uso de inteligencia artificial

Durante el desarrollo de la práctica se ha utilizado inteligencia artificial como herramienta de apoyo. Su uso se ha limitado a tareas auxiliares y de orientación, manteniendo la integración final dentro del diseño del proyecto.

En concreto, se ha usado para:

  • Obtener recomendaciones sobre la estructura del proyecto, separación en ficheros fuente, cabeceras, organización del Makefile y limpieza general del código.
  • Generar borradores de los mensajes de ayuda que se muestran con consultas como ?SUM, ?IF o ?ABS.
  • Apoyar la implementación del acceso a rangos, especialmente en funciones auxiliares como la conversión de referencias a coordenadas y la generación de las celdas intermedias de un rango.
  • Revisar y refinar partes de la documentación de la memoria.

Para ello, se ha usado el agente Codex de Open AI.

About

Implementación de un intérprete interactivo tipo REPL del lenguaje de consultas sobre hojas de cálculo cell. Trabajo final de la asignatura de Gramáticas y Lenguajes Formales (GLF) del curso 2025-2026.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Contributors

Languages