Tabular 2
=========

2026-10-06 (fuentes, posterior a 2.0-13)
  * Fechas completas en epoch, con hora y fracciones, usando mem_util
    corregida (r2465). Sin zona explicita toma la de la maquina/TZ.
    Una fecha invalida no se convierte en su prefijo numerico.
  * t_fechas prueba carga desde archivo y texto, CSV, zonas horarias,
    interpolacion entre horas y unidades day/hr. El corpus fija UTC para
    que sus referencias numericas no dependan de la maquina.
  * Manual LaTeX actualizado desde la pagina, ejemplo2.c separado.
  * Pendiente: texto en celdas y busqueda de vecinos numericos validos
    para interpolar, sin implementar esa politica todavia.

2026-10-06 (2.0-13)
  * Lectura binaria POS antiguo y POS4 de SCEPTRE, por contenido:
    REAL*4 IEEE en ambos ordenes de bytes, corridas independientes,
    componentes AC y rechazo de datos corruptos sin cargas parciales.
  * interpola --exportar-simul exporta esos resultados y los formatos
    numericos de SPICE a .simul con 17 cifras, bloques y unidades.
  * sceptre-tabular ejecuta una instalacion compilada nueva o antigua
    en temporales, o convierte un binario existente, sin sobrescribir
    salidas. Manual propio sceptre-tabular(1). Python 3 para el lanzador;
    gfortran y SCEPTRE son necesarios solo para simular, no para convertir.
  * Las pruebas generales incluyen t_pos4 y t_simul.py. Las integraciones
    opcionales prueban escritores POS000 originales y ejemplos SC1/SD2.
    README describe formatos, limites, pruebas y el parche historico.
  * Los ejemplos importados se versionan en siru/corpus, con procedencia
    y hashes; no se instalan como parte de los paquetes de Tabular.

2026-10
  Que lee y como (corpus/README.md tiene un ejemplo de cada uno):
    - el .simul de siru, lo de SCEPTRE en columnas, el .print de ngspice
      (con paginas y encabezados repetidos), wrdata;
    - el raw ASCII de ngspice (src/raw.y): cada analisis una tabla, y con
      Flags: complex cada variable en dos columnas, NOMBRE.re y NOMBRE.im;
    - gnuplot: las lineas en blanco separan bloques y cada bloque es una
      tabla, ARCHIVO el primero y ARCHIVO#2, ARCHIVO#3... los demas (en
      interpola: leer datos.dat#2); un bloque sin nombres usa los del
      anterior;
    - separadas por blancos, tabuladores o comas; y con ; (un .csv de
      planilla en castellano), donde la coma es decimal: 1,5 es 1.5;
    - \r, \f y \v son blancos: el ultimo nombre de un archivo con \r\n
      ya no lleva el \r pegado.
  El filtro de los listados de SPICE lo hace la lectura sobre los renglones
  de la gramatica (antes era una pasada a mano sobre el texto).
  Un renglon de unidades debajo del de nombres (# min tempC mmHg) pasa los
  valores al SI al leer, con libunidades (GNU units como biblioteca,
  siru/unidades); get_column_unit da la unidad de cada columna, la del
  archivo y la del SI. Antes las unidades se tomaban como nombres.

  Lo que tabular2 guarda entre llamadas (las tablas cargadas, el detalle del
  ultimo error) es de un contexto, struct tb_ctx: tb_ctx_nuevo, tb_ctx_usar
  y tb_ctx_liberar (table.h). Cada hilo tiene el suyo; la API de siempre
  anda igual y dos hilos no se pisan las tablas. Prueba tests/t_hilos.

  corpus/: una tabla de cada formato y lo que da cada una (corpus/compara,
  en el make check). Se congelo antes de estos cambios, asi que cada arreglo
  se ve como un cambio de lo esperado en el historial.

  La tabla y las ordenes de interpola se leen con slex y Bison: src/lectura.y
  (renglones con un # opcional, celdas y un comentario; cual # es el de los
  nombres, con las reglas de siempre) y src/ordenes.y (ordenes separadas por
  ;, COL=VALOR, > ARCHIVO, # hasta el ;). Parsers puros, cada uno con su
  contexto de slex. Reemplazan la maquina de estados de manual_lexer.c y el
  corte con strtok de interpola.c. Lo bien escrito da lo mismo (las 136
  tablas del arbol, siru sobre los circuitos con tablas). Cambia:
    - una tabla sin salto de linea al final ya no pierde su ultima fila;
    - en un .csv dos blancos seguidos dentro de una celda ya no la parten
      (724673_2010_solar.csv tenia una columna de mas en los nombres);
    - una orden rara de interpola (COL=VALOR con algo de mas, dos >) se
      avisa y no se ejecuta a medias.
  Hace falta libslex-dev y bison para armar.

  La lectura de tablas tira las lineas vacias o de solo blancos, que antes
  entraban como una fila de ceros, y entiende los listados que escribe
  ngspice (SPICE) con .print: preambulo con titulos y fecha, encabezado de
  columnas entre lineas de guiones repetido en cada pagina, y epilogo de
  estadisticas. Queda solo la tabla; el encabezado pasa con '#' adelante y
  las columnas se piden por nombre (time, v(in), v(c)). Prueba nueva:
  tests/spice.txt, en t_interpola.

2026-09 (2.0-9)
  * create_table_from_string(nombre, texto, &nt): una tabla escrita en
    una cadena, con el mismo formato que un archivo, registrada con un
    nombre; despues interpolate(nombre, ...) la usa como a un archivo. La
    lectura sigue siendo la misma: load_table y busca_encabezado trabajan
    ahora sobre el FILE* ya abierto (antes busca_encabezado volvia a abrir
    el archivo por su nombre), y el texto se abre con fmemopen. Lo pidio
    Calcula, para las tablas dentro de un .din. Prueba tests/t_texto.

2026-09
  La biblioteca quedo utilizable de punta a punta desde ExprTk.

  * interpolate() volvia a la nada: la llamada a get_numbers estaba
    comentada y num_table y num_view quedaban fijos en 0, asi que se
    evaluaba sobre una lista de tablas vacia y el proceso se caia. Se
    rehizo con la secuencia crear, cargar, armar vistas y buscar la vista
    del par de columnas, que es la que usa examples/02.

  * Se repusieron funciones que estaban comentadas o que no existian, con
    el mismo contrato que en interpola y tabular: search_header,
    create_search_table, create_search_view, create_search_view_num,
    get_numbers, get_numbers_num, interpolate_file_num, interpolate_num,
    finddir, get_qty_tables, get_table_info, get_table_line_info,
    get_view_info y free_tablelist.

  * Los diez headers llevan guardas de inclusion y extern "C". Hacia falta:
    src/exprtk/exprtk_table.cpp los consume desde C++.

  * Vocabulario de errores unificado con interpola y tabular, en
    src/table.h. Cero es exito y los negativos son error.

  * Y ahora esa convencion se cumple en todo el arbol. Quedaban doce
    funciones que devolvian 1 para decir que habian andado --entre ellas
    interpolate(), interpolate_view() y load_table()-- conviviendo con
    treinta y cinco que devolvian 0. Todas devuelven 0. Se reviso antes
    que ningun llamador comparara contra 1: los tres de afuera del
    paquete (src/exprtk/simusol_exprtk.cpp, terminal/src/main.c y
    fortran/example_table.c) prueban error < 0, que sigue valiendo.

  * La biblioteca dejo de escribir en la salida del programa que la usa.
    Contados en bruto habia 93 printf, pero 32 estaban adentro de
    comentarios y 38 son las funciones de volcado --print_table,
    print_cell, print_list_tables, print_index_table, print_views y
    get_view_info--, donde la salida es lo que el llamador pidio. Las 23
    que quedaban eran trazas de depuracion y ahora van a stderr detras
    de verbose.

    Quince de ellas tenian arriba un "if (verbose>10) {}" con el cuerpo
    vacio: alguien empezo la conversion y las llaves se comieron el
    printf, que quedo suelto en la linea siguiente. Esos guardas vacios
    se fueron y el printf quedo adentro de la condicion que le
    correspondia.

    De paso, la traza de create_view_num llamaba dos veces a print_cell:
    una adentro de un <%s>, que no recibia la celda sino "" porque
    print_cell la imprime por su cuenta, y otra en la linea siguiente.
    Las dos escriben en stdout, o sea que una traza de depuracion
    ensuciaba la salida del programa. print_cell es de volcado y no sirve
    para trazar; las dos llamadas se fueron y la traza quedo en stderr.
    Ahora la salida de print_table es la misma con verbose 0 que con
    verbose 11, y las 3129 lineas de traza van todas a stderr.

  * Un numero de tabla que no existe ya no tumba el proceso. Era lo mismo
    que en print_cell pero en grande: get_table_pointer comprueba el rango,
    avisa por *error y devuelve NULL, pero 38 de sus 39 llamadores
    desreferenciaban de una. Sondeadas 36 funciones publicas con un numero
    de tabla inexistente, 18 de las primeras 20 se caian con señal 11.
    Ahora ninguna se cae y las 36 avisan.

    Se pusieron 46 comprobaciones de NULL: 37 en table.c, 1 en cell.c y 8
    en view.c, estas ultimas detras de get_view_pointer, que tenia el mismo
    problema un nivel mas arriba --get_tama_vista y get_num_table_view
    desreferenciaban sin mirar, y por ahi se caia interpolate_view--.

    Y get_table_pointer misma tenia dos agujeros. Sin ninguna tabla
    cargada, dpointers es NULL y occupy vale 0, de modo que la tabla 0
    pasaba el control de rango y se desreferenciaba un arreglo que no
    existe; ahora se comprueba stinicial. Y una tabla descargada, o dejada
    en NULL al fallar la carga, deja un hueco en el arreglo que tampoco se
    miraba.

    Dos funciones no se caian pero mentian: interpolate_view devolvia exito
    con un numero de tabla malo, y print_views imprimia el encabezado y
    devolvia cero. interpolate_view comprueba ahora la vista en el borde de
    la biblioteca, igual que get_view_info.

    Cubierto en tests/t_errores.c, que sin los arreglos termina en
    violacion de segmento.

  * El deshacer de create_search_table perdia veinte bytes por cada archivo
    que no se pudo abrir: liberaba el name y la tabla, pero no los
    files_pointers que new_source ya habia reservado. El cuerpo por tabla
    de free_tablelist se saco a free_table(), que ahora usan las dos.
    Medido con valgrind sobre los cinco programas de tests: 0 bytes
    perdidos y 0 errores en todos. Antes eso se habia medido solo sobre una
    carga y una liberacion, que no pasa por ese camino.

  * print_cell se caia con una celda que no existe. La primera linea de
    la funcion decia

      if (generic_cell==NULL && isheader && generic_cell->tipocelda!='s')

    o sea que comprobaba si la celda es NULL y en la misma condicion la
    desreferenciaba. Con isheader distinto de cero y una celda que no
    existe, el proceso se caia; y como el && cortaba antes, tampoco se
    hacia ninguna de las dos comprobaciones, asi que una celda NULL
    seguia de largo y reventaba unas lineas mas abajo igual. Son dos
    cosas distintas y ahora van separadas: celda NULL devuelve
    TB_INVALID_ARGUMENT, y un encabezado que no es texto solo avisa,
    porque cortar el volcado esconderia la celda. Cubierto en
    tests/t_errores.c, que sin el arreglo termina en violacion de
    segmento.

    Y al reves, adentro de print_cell las celdas de texto ('s' y 'c') de
    la rama all!=0 escribian en stderr y solo con verbose>10, mientras
    las numericas de al lado escribian en stdout siempre. Las cuatro
    ramas van ahora a stdout: print_cell es de volcado y la salida es la
    que pidio el llamador.

  * Los ejemplos se enlazan contra el arbol de fuentes. Antes apuntaban a
    /usr/local/lib, de modo que un cambio en src/ no se notaba en ellos
    hasta hacer make install, y era facil probar sin darse cuenta una
    version vieja. Ahora los encabezados salen de ../src y la biblioteca de
    src/.libs/libtabular2.a, la estatica; las banderas estan en un solo
    lugar, examples/comun.mk, y el ejemplo le pregunta siempre a src/ si
    hay algo que recompilar, asi que el cambio llega solo. Con la estatica
    tampoco hace falta rpath ni LD_LIBRARY_PATH para correrlos.

    Un make -C examples arma los dos que se pueden armar. El 02 interpola
    la salida de una simulacion de Simusol que viene al lado, y el 04
    evalua Table() en una expresion de ExprTk y da los mismos 32.02833 y
    32.07023 de siempre. El 04 compila ../../src/exprtk/exprtk_table.cpp,
    el fuente que mantiene el paquete, y se borro la copia que tenia al
    lado: era de abril de 2024, no definia verbose ni archivoenuso --asi
    que no enlazaba-- y usaba una variable sin inicializar donde va la ruta
    del ejecutable. Mantener dos veces lo mismo es como se llego a eso. El que era el 01 paso a viejos/ejemplo-01, con un LEAME que
    cuenta todo lo que se averiguo de el. Ahora compila: calc.y y flex.l, el analizador
    que llama en main.c, vinieron de svn/siru/flex con svn move (ese
    directorio esta ahora en viejos/flex). Para que
    compilara hubo que sacar de flex.l una copia de quees() que chocaba
    con la de la biblioteca --"definiciones multiples" al enlazar, y ya se
    habia quedado atras, llamaba a datetime() con cuatro argumentos cuando
    hoy lleva cinco-- y un ";" suelto despues del segundo %% que hacia
    invalido el C generado. Ninguna de las dos se veia: el Makefile de
    svn/siru/flex si genera el .c a partir de flex.l, pero su lexer1 no se
    construye desde antes de 2021 --falta view.c y table.c no compila-- asi
    que nunca se llegaba a compilarlo. Lo que no se sabe es que entrada
    espera: el analizador define un lenguaje propio con \table(nombre),
    \include(archivo) y \tt(...), y las entradas que se probaron dan
    "syntax error, expecting NEWTABLE". Por eso no entra en la prueba.

  * Se pueden comentar los archivos de datos. Antes toda linea que
    empezara con # antes de los datos se leia como los nombres de columna,
    asi que un comentario arriba del archivo se tomaba como encabezado; con
    las columnas corridas, buscar una por nombre terminaba en violacion de
    segmento. Ahora el encabezado se elige por la cantidad de palabras, que
    tiene que ser la de la primera linea de datos, y "##" es siempre
    comentario. Una linea que es solo un comentario ya no cuenta como linea
    de la tabla: si contara, correria la numeracion y los encabezados
    dejarian de estar en la linea 0, que es donde los busca search_header.
    Cubierto en tests/comentada.txt.

  * Lee tal cual los archivos de resultados de Simusol. Los .simul que
    escribe lolisol_sceptre traen un bloque de encabezados anunciado por
    "#  para Simusol: sceptrevars variables magnitudes unidades", donde
    cada palabra nombra en orden a una de las lineas con # que siguen; el
    orden cambia de un archivo a otro. Se usan los sceptrevars, que son
    los nombres con los que se pide una columna en todo Simusol, y las
    demas lineas quedan como comentarios: la de indices tiene numeros y
    si contara como encabezado se colaria una fila 1 2 3 4 entre los
    datos. Antes esos archivos daban NOHEADER: probado con
    data/tanque.simul, tanque.5simul y
    elementos_parametros_concircuito.simul, que ahora dan el mismo valor
    que el .INTER correspondiente.

  * Y la caida en si: get_string_data y otras quince funciones de cell.c
    desreferenciaban lo que devuelve get_cell_pointer sin comprobarlo. Es
    el mismo caso de get_table_pointer, un nivel mas abajo. Ahora
    comprueban.

  * Instala un archivo pkg-config, como mem_util, para que otro proyecto
    pueda compilar contra la biblioteca con PKG_CHECK_MODULES([TABULAR2],
    [tabular2]). Lo usa calcula (svn/psicro), que desde su 6.0-18 ofrece
    tabla('archivo','X','Y',valor[,metodo]) en las expresiones de sus
    archivos .din.

  * Se fue src/checkdir/, la prueba de abril de 2024 hecha con el
    framework Check y con nombres de la epoca de simusol_table, que
    estaba fuera de la compilacion desde entonces. La reemplaza tests/.
    Con ella se fueron de examples/02 las copias propias de tabular.h y
    tabular_internal.h --ningun fuente las incluia, interpola.c usa los
    headers del paquete-- y el binario interpola, que estaba versionado
    por error.

  * exprtk.hpp actualizado a la version del 20250101. Antes se esperaba en
    /tmp/down, que no existia, de modo que src/exprtk no se podia
    compilar; despues se guardo adentro del paquete, y ahora sale del
    paquete libexprtk-dev, que instala la unica cabecera en /usr/include.
    Una copia propia por proyecto obliga a mantenerlas al dia a mano.
    mem_util y exprtk salen las dos de pkg-config en src/exprtk/Makefile,
    que ya no tiene rutas escritas a mano a ../../../local.

  * src/exprtk/Makefile rehecho: apuntaba a ../libs/tabular.o y a un
    makefile.inc que en tabular 2 no existen.

  * Ninguna estructura se inicializaba al crearse: add_memory usa malloc y
    no limpia, asi que los punteros de los arreglos todavia no asignados
    tenian basura del heap en vez de NULL. Se limpian struct table y
    struct cell al crearlos, y los huecos de line[].

  * Sin fugas de memoria. Medido con valgrind sobre una carga y una
    liberacion: 0 bytes perdidos, 0 errores. Se venia de 7638 bytes en
    1444 bloques. Las causas eran cinco: los int por linea y los nombres
    de archivo que nadie liberaba, archivoenuso que se re-reservaba
    pisando el anterior, el lexer que duplicaba cada token dos veces
    porque create_cell ya copia, la cadena que quees reserva con %m[ y el
    asprintf del nombre de cada vista.

  * Un archivo inexistente ya no termina el proceso: devuelve FO. Antes
    open_file de mem_util hacia exit(-1).

  * Pruebas. Hay cinco programas en tests/ con 219 comprobaciones, que se
    corren con make check. Antes no habia ninguna automatizada: solo
    programas sueltos en examples/ y src/checkdir/, fuera de la
    compilacion. Al escribirlas aparecieron dos defectos:

      - create_search_table dejaba registrada la tabla aunque el archivo no
        se pudiera leer, de modo que la busqueda siguiente la encontraba
        por nombre y devolvia una tabla vacia como si estuviera cargada.

      - get_view_info se caia con un numero de vista inexistente, porque
        get_view_pointer no comprueba el rango y devuelve basura sin avisar.

  * Una tabla de dos columnas terminaba el proceso. create_views tomaba
    max_col, que es el indice de la ultima columna, como si fuera la
    cantidad, de modo que dos columnas se rechazaban como "hay una sola",
    y ademas con exit(1). Es el caso natural para interpolar, y el unico
    posible para una tabla con saltos. Se corrigio el conteo y los tres
    exit() de view.c pasaron a devolver TB_NO_DATA, SC y MC.

  * La spline devolvia un valor incorrecto en el ultimo punto de la tabla
    cuando habia un salto al principio. La rama del extremo derecho de
    evaluate_spline leia la fila 0 en vez de la ultima: era la rama del
    extremo izquierdo copiada sin cambiarle el indice. Con la tabla
    0,10 -> 0,12  1,20  2,30, consultar x=2 devolvia 12 en vez de 30.
    Cubierto en tests/t_saltos.c.

  * Se sacaron los exit() que quedaban en la biblioteca: cinco en view.c y
    uno en cada uno de cell.c, sequence.c, table.c y tables.c. Ahora
    informan con el codigo de retorno. Los dos accesores, get_table_pointer
    y get_view_pointer, devuelven NULL avisando por *error en vez de
    terminar el proceso; eso es lo que permite que get_view_info rechace un
    numero de vista inexistente en lugar de caerse.

  * Una tabla con las abscisas al reves o desordenadas devolvia resultados
    falsos informando exito: la decreciente daba -nan y la desordenada
    devolvia siempre el primer valor de la columna, las dos con codigo 1.
    binsearch supone que X no decrece y nadie lo comprobaba. Ahora se
    verifica al pedir la vista y se rechaza con SC. Los X repetidos, que
    son los saltos, siguen siendo validos.

  * interpolate_view ignoraba el valor que devolvian evaluate_lineal y
    evaluate_spline y contestaba 1 siempre, aunque no se hubiera escrito
    nada en *presult. Ahora lo propaga.

  * La spline ante una tabla con saltos es una secuencia de splines, una
    por tramo continuo: en cada discontinuidad se impone condicion natural
    a ambos lados. El mecanismo ya estaba en derivative(); lo que faltaba
    era documentarlo y probarlo.

  * Documentada y cubierta la semantica de los saltos: repetir una
    coordenada X representa una discontinuidad, con el valor por izquierda
    y por derecha, y el promedio en el punto exacto. Ver la seccion
    correspondiente del README y tests/t_saltos.c.

  * mem_util se busca con pkg-config (PKG_CHECK_MODULES), de modo que ya no
    hace falta pasarle a configure la ruta de sus cabeceras a mano.
