Just a little bit freak

sábado, 15 de agosto de 2015

Rooteo de la tablet Energy Sistem NEO2

En esta entrada explicamos el proceso seguido para rootear una tablet Energy Sistem NEO2 como esta:

Por defecto la tablet está protegida por el fabricante, si intentas acceder con el superusuario "root", obtendrás este mensaje de error:

    $ adb root
    adbd cannot run as root in production builds

Los fabricantes son rehacios a dejar que los usuarios normales tenga acceso root a sus dispositivos, ya que no les gusta que los usuarios normales puedan customizar sus dispositivos (y tampoco que usuarios que no saben lo que hacen los puedan estropear). Si no eres root, tu capacidad de hacer modificaciones en tu dispositivo estará bastante limitada. La intención de esta articulo es hacerse root de la tablet Energy Sistem NEO2. Usaremos como sistema host un Ubuntu Linux 14.04. Advierto que el procedimiento es altamente destructivo si no se hace con cuidado. Advertido quedas, lector!

Averigua el product-id y el vendor-id

Conecta la tablet a tu PC con el cable USB y mira la salida del comando "lsusb":

    $ lsusb

Identifica en el listado una linea como esta:

    Bus 003 Device 066: ID 2207:0011

El ID 2207:0011 se corresponde con tu tablet Energy Sistem de 10.1 pulgadas.

Acceso al dispositivo a través de USB

Configurar el sistema operativo de tu PC para que tenga acceso al dispositivo Android a través del USB. Bajo Ubuntu, los usuarios regulares no pueden acceder directamente a los dispositivos USB por defecto. El sistema necesita estar configurado para permitir tal acceso. La mejor solución consiste en crear un fichero (como usuario root):

    $ sudo vi /etc/udev/rules.d/51-android.rules

Y añadir el siguiente contenido:

    SUBSYSTEM=="usb", ATTR{idVendor}=="<idvendor>", ATTR{idProduct}=="<idproduct>", MODE="0600", OWNER="<username>"

Donde:

  • <username>: es el nombre del usuario que estará autorizado a acceder al dispositivo a través del USB
  • <idvendor>: es el identificador del vendedor del dispositivo
  • <idproduct>: es el identificador del producto

Para el dispositivo que nos ocupa:

    <username> es "aicastell"
    <idvendor> es "2207"
    <idproduct> es "0011"

Por tanto, añade este contenido:

    # adb protocol
    SUBSYSTEM=="usb", ATTR{idVendor}=="2207", ATTR{idProduct}=="0011", MODE="0600", OWNER="aicastell"

Cambia los permisos del fichero:

    $ chmod a+r /etc/udev/rules.d/51-android.rules

Reinicia el servicio del udev:

    $ sudo service udev restart

Estas reglas nuevas pasan a tener efecto la siguiente vez que se conecta un dispositivo. Por tanto, será necesario desconectar el dispositivo y volver a conectarlo al USB.

Customización especial

Este paso no es habitual en todos los dispositivos Android, pero SI es necesario para los dispositivos SOC Rockchip. Si no lo haces, el comando "adb devices" no detectará el dispositivo Android conectado. Crea este directorio:

    $ mkdir ~/.android

Y crea este fichero, con este contenido:

    $ vi ~/.android/adb_usb.ini

    # ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.
    # USE 'android update adb' TO GENERATE.
    # 1 USB VENDOR ID PER LINE
    0x2207

Habilitar el modo desarrollador Android

En tu dispositivo Android, navega hasta el menu de configuración y busca la opción:

    { } Opciones de desarrollo

Si no tienes acceso a estas opciones, ves al apartado:

    Ajustes > información de la tablet > Numero de compilación

Pulsa varias veces sobre la opción "Numero de compilación" hasta que se active la opción "Opciones de desarrollo".

Ahora navega a traves de estas opciones:

    "Opciones de desarrollo" > "Depuración" > "Depuración USB"

para activar el modo de depuración cuando el dispositivo este conectado por USB.

Android Debug Bridge (ADB)

La herramienta “adb" es esencial para depurar, manejar y personalizar dispositivos Android. "adb" son las siglas de Android Debug Bridge. Esta herramienta esta disponible a partir de Ubuntu 14.04 como binario precompilado. Puedes instalarla usando este comando:

    $ sudo apt-get install android-tools-adb

También se podría compilar descargando los fuentes del Android Open Source Project (AOSP), aunque no es el objetivo de este documento.

Detección del dispositivo

Cuando conectes el dispositivo Android por USB a tu PC, podrás verificar que tu dispositivo esta conectado ejecutando el comando:

    $ adb devices

Desde Android 4.2.2 en adelante se introdujo una nueva caracteristica de seguridad. Debes confirmar que tu dispositivo android esta conectado a un PC autorizado antes de que se permita ningún dialogo. Por tanto, si es la primera vez que lanzas este comando, verás esta salida:

    List of devices attached

    d1548bec06f16120    unauthorized

En este caso, en el dispositivo de Android saldrá una ventana de dialogo preguntando si quieres aceptar la clave RSA que acepte la depuración del dispositivo a través del PC desde el que has conectado. Este mecanismo de seguridad protege al dispositivo frente a ataques remotos, porque asegura que el debugging USB y otros comandos adb no puedan ser ejecutados a menos que seas capaz de desbloquear el dispositivo confirmando este dialogo.

Si no es la primera vez que lanzas el comando, verás esta salida, sin mas:

    $ adb devices
    List of devices attached
    0123456789ABCDEF    device

El problema

El fabricante protege la tablet y no podremos lanzar este comando, ya que genera error:

    $ adb root
    adbd cannot run as root in production builds

El objetivo de este post es encontrar una solución a este problema, así que vamos a analizar este error. Lo primero es buscar en los fuentes de Android (proyecto AOSP) el string del error. Encontramos el mensaje en este fichero:

    $ vi aosp/system/core/adb/services.c

    property_get("ro.debuggable", value, "");
    if (strcmp(value, "1") != 0) {
        snprintf(buf, sizeof(buf), "adbd cannot run as root in production builds\n");
        writex(fd, buf, strlen(buf));
        adb_close(fd);
        return;
    }

Como vemos, el método "property_get" lee el valor de la propiedad "ro.debuggable" y si su valor no es 1, muestra el error "adbd cannot run as root in production builds".

Buscando mas a fondo en los fuentes de Android, encontramos que la función property_get esta definida en este fichero:

    $ vi aosp/system/core/libcutils/properties.c

    int property_get(const char *key, char *value, const char *default_value)
    {
        int len;

        len = __system_property_get(key, value);
        if(len > 0) {
            return len;
        }

        if(default_value) {
            len = strlen(default_value);
            memcpy(value, default_value, len + 1);
        }
        return len;
    }

Por el nombre del método __system_property_get, deducimos que se trata de una propiedad de la partición "system" de Android. Por tanto, el primer objetivo será obtener el contenido de la partición system de la tablet, para modificar su fichero de propiedades (build.prop), añadiendo en su interior la propiedad ro.debuggable=1.

La herramienta rkflashtool

La herramienta "rkflashtool" permite leer/escribir el contenido de cualquier partición de la tablet. Para instalar esta herramienta, lo primero es instalar las dependencias de Ubuntu necesarias para su compilación:

    $ sudo apt-get install build-essential libusb-1.0-0-dev

Descarga la herramienta:

    $ git clone https://github.com/linux-rockchip/rkflashtool

Compila e instala:

    $ cd rkflashtool
    $ make
    $ sudo cp rkflashtool rkcrc rkunpack rkunsign /usr/local/bin

Despues arrancas la tablet en modo bootloader:

    $ adb reboot bootloader

Y ya puedes obtener el layout de la memoria flash con este comando:

    $ rkflashtool p > parameters.txt

En mi caso concreto, tengo este layout:

    NAME OFFSET NSECTORS
    misc 0x00002000 0x00002000
    kernel 0x00004000 0x00006000
    boot 0x0000a000 0x00006000
    recovery 0x00010000 0x00010000
    backup 0x00020000 0x00020000
    cache 0x00040000 0x00040000
    metadata 0x00080000 0x00002000
    kpanic 0x00082000 0x00002000
    system 0x00084000 0x00200000
    vendor 0x00284000 0x00002000
    userdata 0x00286000 -

Ahora puedo leer el contenido de cualquier partición con este comando:

    $ rkflashtool r <nombre_particion> > <nombre_particion>.img

Y puedo grabar el contenido de cualquier partición con este comando:

    $ rkflashtool w <nombre_particion> <nombre_particion>.img

Añadir ro.debuggable=1

Para obtener el contenido de la partición de system hacemos:

    $ sudo rkflashtool r system > system.img

Vemos que system.img es la imagen de un sistema de ficheros en formato ext4:

    $ file system.img
    system.img: Linux rev 1.0 ext4 filesystem data, UUID=da594c53-9beb-f85c-85c5-cedf76546f7a, volume name "system" (extents) (large files)

Podemos montar esta partición system.img en modo loopback:

    $ sudo mount -o loop system.img /media/removable

Cambiar el contenido del fichero build.prop:

    $ vi /media/removable/build.prop
    ro.debuggable=1

Tras desmontar la partición:

    $ sudo umount /media/removable

Vuelve a grabar el contenido con este comando:

    $ rkflashtool w system < system.img

Sin embargo, el problema sigue manifestándose:

    $ adb root
    adbd cannot run as root in production builds

Con esto deducimos que el cliente "adb" conecta con un servicio de la tablet (llamado adbd), que se encarga de decodificar el contenido del fichero build.prop y proporcionar una respuesta al cliente "adb". Si el fichero build.prop tiene la propiedad ro.debuggable=1 y sigue sin funcionar, debe ser porque este servicio adbd esta capado, y siempre devuelve error, independientemente del contenido del fichero de propiedades build.prop.

La herramienta imgrepackerrk

Para solucionar este problema intentaremos reemplazar el adbd capado por uno no-capado. Para ello lo primero es averiguar donde está almacenado el adbd capado. Tras buscar adbd en la partición system, no lo encontramos. Podría estar almacenado en la partición de boot, así que lo primero es leer su contenido con la herramienta rkflashtool:

    $ rkflashtool r boot > boot.img

Vemos que la imagen de boot (boot.img) no se puede montar, pues se trata de un fichero de datos, no de un sistema de ficheros:

    $ file boot.img
    boot.img: data

Necesitamos una herramienta para extraer el contenido de esta partición. En este foro de xda-developers:

    http://forum.xda-developers.com/showthread.php?t=2257331

encontramos la herramienta "imgrepackerrk" (versión 104). Esta utilidad permite desempaquetar/reempaquetar imágenes de boot.img.

Tras descargar la herramienta, la instalámos en nuestra máquina, copiandola en /usr/local/bin:

    $ cp imgrepackerrk /usr/local/bin
    $ chmod 755 /usr/local/bin/imgrepackerrk

Usamos esta herramienta para extraer el contenido de la imagen boot.img:

    $ imgrepackerrk boot.img

Una vez desempaquetado el contenido de boot.img, buscamos el servicio adbd en su interior:

    $ find boot.img.dump/ | grep adbd
    boot.img.dump/ramdisk.dump/sbin/adbd

Efectivamente, el servicio se encuentra dentro de esta partición. Este fichero es el que creemos que está capado, y hay que reemplazar por un servicio adbd que no este capado.

El servicio adbd no-capado

El servicio adbd no-capado lo vamos a obtener descargando el paquete adbd-Insecure-v2.00.apk desde esta URL:

    http://forum.xda-developers.com/showthread.php?t=1687590

El checksum del archivo descargado es este:

    $ md5sum adbd-Insecure-v2.00.apk
    df9ef24983dd29530bbbe2a7caeb35d8  adbd-Insecure-v2.00.apk

Descomprime su contenido:

    $ unzip adbd-Insecure-v2.00.apk

Dentro de este apk encontraremos el nuevo adbd no-capado, que curiosamente tiene una extensión .png (normalmente usada para fotos), aunque vemos que en realidad no se trata de una foto, se trata de un binario ejecutable:

    $ file ./assets/adbd.21.png
    ./assets/adbd.21.png: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, stripped

Reemplaza este archivo adbd.21.png por el adb capado:

    $ cp ./assets/adbd.21.png boot.img.dump/ramdisk.dump/sbin/adbd

Reempaqueta la imagen boot.img con el nuevo servicio adbd no-capado:

    $ imgrepackerrk boot.img.cfg

El comando anterior reempaqueta la imagen boot.img con un adbd no-capado que esperamos que resuelva nuestro problema inicial.

Graba el nuevo boot.img en la flash:

    $ rkflashtool w boot < boot.img

Disfrutar del resultado

Reinicia la tablet. Si todo ha ido bien podrás disfrutar de tu tablet rooteada:

    # adb root
    adbd is already running as root

    # adb remount
    remount succeeded

viernes, 17 de julio de 2015

Un sueño hecho realidad: Skoda Triathlon Castellon 2015

Mi estreno en el Triathlon Olímpico

Era Viernes por la tarde de un 25 de Marzo de 2011 (hace ya mas de 4 años). Un día de esos que llegaba cansado del trabajo y me apetecía escribir algo en mi blog. Era una de esas entradas dedicadas a temas deportivos, ese día escribí estas lineas:

    "En mi cabeza resuena con fuerza la palabra 'Triathlon'. 
     Me atrae mucho la idea de juntar la bicicleta, piscina
     y el running en una sola prueba".

Aunque ya practicaba el running desde hacía algunos años, no tenía bicicleta y tampoco sabía nadar. Pero esa frase quedó grabada en mi cabeza. Sabía que faltaba mucho para poder siquiera intentarlo, pero la semilla estaba plantada.

En Diciembre de 2011 compré una MTB Trek 4900 (mi única bicicleta hasta la fecha). En aquel momento empecé a usarla con la única intención de llegar antes al trabajo, pues andando tenía 25 minutos de ida y otros tantos de vuelta, y me pareció buena idea lo de moverme por la ciudad en "bici", para ganar algunos minutos al día y poder dedicarlos a cosas mas productivas que a ir/volver del trabajo.

Y la verdad es que fue una inversión muy acertada. El tiempo para ir y volver del trabajo se redujo de 50min/día a solo 20min/día, asi que ya disponía de 30min/día que antes no tenía. Esto me permitió poder tener mas tiempo libre para entrenar y para descansar.

A principios de 2012 mi vecino Leo se enteró de que me había comprado una bici. El tenia su MTB abandonada en la terraza hacía tiempo, y al enterarse de mi compra se decidió a poner a punto la suya. Y con la excusa nos animamos a salir algún Sábado por la mañana a hacer alguna ruta con la bici. Empezamos con rutas cortas, y poco a poco fuimos incrementando la distancia. Por desgracia mi vecino tuvo una lesión grave en la rodilla y tuvo que dejar la bici. A mi en aquel momento no me apetecía salir solo con la bici y dejé de entrenar con la MTB para centrarme exclusivamente en correr y en el gimnasio.

Pasaron los años 2012 y 2013 sin hacer mucho caso a la MTB mas que para ir/ volver cada día a/del trabajo. Seguía entrenando musculación en el gimnasio Shen Dragon y saliendo a correr 4/5 días por semana. Me planteé correr mi primer Maratón a finales de 2013, reto que conseguí en Diciembre de 2013 tras sufrir mucho con una tendinitis de rodilla. Pero el Triathlon seguía dándome mucho respeto, hasta el punto de plantearme que no llegaría a intentarlo nunca, que quizás no estaba hecho para pruebas tan extremas y que con el Maraton había alcanzado mi limite.

El 16 de Junio de 2014 corrí la IV volta a peu del Grau de Castelló, una cursa popular de 5K en la que también participó mi amigo Javi. Era su estreno como runner popular y en su primer 5K consigió bajar de 30 minutos. Le felicité por dos cosas, primero por su cambio de habitos (de una vida completamente sedentaria a nuevo deportista) y segundo por la marca conseguida (bajar de 30m en su primer 5K esta pero que muy bien!). Tras terminar la cursa y ya de camino al coche, Javi me confesó que había disfrutado mucho corriendo esta carrera, que el ambiente le había encantado. Así que iba a plantearse mejorar la marca para el año que viene y a continuar entrenando para conseguir retos mas ambiciosos. En ese momento de subidón yo me animé también y le dije:

    "Pues venga! Por que no? Para el año que
     viene (2015) yo me preparo el Triathlon!"

No me gusta prometer cosas que luego no podré cumplir, y se que esta vez me acabo de meter en un buen berenjenal. Y es que la palabra "Triatleta" me sigue dando mucho pero que mucho respeto desde hace años. Pero (por fin!) decido que ha llegado el momento. Pienso tomarme el reto como una experiencia vital en la que tendré que aprender a nadar, tendré que empezar a entrenar bici en serio, y tendré que mantener un nivel aceptable de carrera a pie. Se que será un proceso largo y duro, pero a la vez emocionante y divertido, puesto que cada día entrenaré una modalidad distinta, no habrá un solo día en el que repita el mismo entrenamiento del día anterior. Lo pienso bien y veo claro que no hay nada que me apetezca mas en estos momentos que intentarlo. Así que queda decidido, durante el próximo año voy a preparar mi primer Triathlon.

La primera herramienta que necesito es un reloj con GPS para poder llevar cierto control sobre las distancias/desniveles de todos los entrenamientos realizados. Dudo entre el Suunto Ambit2S y el Polar V800. El diseño del V800 es muy bonito pero es un reloj bastante caro y no tiene soporte para Linux. El día 4 de Julio de 2014 decido comprar el Suunto Ambit 2S Graphite, con unas increibles prestaciones practicamente idénticas a las del Polar, pero a mitad de precio. 4 días mas tarde recibo esta joyita sin la que hoy en día soy incapaz de vivir:

Se trata de un reloj inteligente con multitud de funciones para correr, ir en bici, y nadar. Esta equipado con GPS y integrado con multitud de sensores para medir distancia recorrida, altitud, velocidad media y maxima, cadencias de depedaleo, pulsometro, contador de pasos, brazadas en natación, y mas cosas que ni siquiera uso. Lleva una memoria interna que registra los entrenamientos realizados y los ordena por fecha. Se acabó lo de medir las distancias de los entrenamientos "a palmos" con mi antiguo casio F-91W, nos modernizamos!

El siguiente paso consiste en aprender a nadar. Suena un poco a chiste, ¿aprender a nadar yo? Si corriendo he sido capaz de acabar un maratón y toda la vida he vivido al lado del mar, mis padres tienen piscina en la villa donde me han criado desde pequeño, pasando mas horas dentro que fuera de ella!

Por lo que nadar para mi no tiene secretos: un brazo, otro brazo, respirar por el lado. Chupao! Mi nivel? Pues medio! Lo que necesito es un curso para perfeccionar (jaja! eso pensaba yo). Al ataque! Por lo que decido contactar con el Club Natació Terrassa y explicarles que busco un entrenador para perfeccionar mi técnica de natación. Tras tomar mis datos, me ponen en contacto con Marc, quien se convertirá durante las siguientes semanas en mi entrenador personal.

Me hago con unas gafas de natación graduadas, y un día mas tarde, el día 15 de Julio a las 18.30h realizo mi primer entrenamiento en la piscina. El lugar elegido por Marc, la piscina exterior de 25m del Club Natacio Terrassa, ya que la piscina Olímpica de 50m esta cerrada por reformas.

Marc comienza el entrenamiento pidiéndome nadar 25m para evaluar mi "tecnica de nivel medio". Al hacer los primeros 25m llego bastante agotado y tal como los termino me pide repetir los 25m de vuelta. Tal y como llego de vuelta ya sin control alguno sobre mi respiración me vuelve a pedir 25m mas. A mitad de la tercera piscina ya sin saber como respirar para poder seguir dando brazadas sin ahogarme, doy mi primer trago de agua, y me atraganto.

    "No et preocupis! Que no te la acabaras tota! :)"

Sonrio mientras sigo nadando como buenamente puedo para llegar al final. Tras terminar mi tercera piscina de 25m me pide volver. No puedo mas. Necesito parar, coger aire y recuperar mis pulsaciones. Hacía tiempo que no me sentía tan agotado. Aterrizo con gravedad 10G sobre la superficie Terrestre. Se supone que el día del Triathlon tendré que nadar 1500m en mar abierto, y no soy capaz de hacer 100m en una piscina climatizada sin acabar completamente exhausto. Vale, esto va a ser muy duro Ivan, mucho mas de lo que imaginaba!

Termino mi primer entrenamiento en la piscina. Apenas 250 metros de nado en algo mas de 30 minutos. Suficiente para darme cuenta que el gimnasio, la bici, el trekking y el running sirven de muy poco en el agua. Hoy he averiguado donde tengo mis dorsales, lo recuerdo cada vez que muevo los brazos (de dolor! jeje). Aún así, al llegar a casa me pongo las zapatillas de correr y decido salir a trotar 7K para acostumbrar al cuerpo a doblar entrenamientos. Sensaciones muy raras, totalmente desconocidas, aunque muy contento porque por fin he iniciado mis entrenos en el agua.

Tras este primer entreno Marc marcha de vacaciones y las semanas siguientes voy 2 o 3 días mas a nadar por mi cuenta, y me trago todos los vídeos en YouTube que encuentro donde enseñan a nadar bien. Cada día que voy a la piscina mejoro un poco respecto el día anterior, pero los primeros días marcho a casa con una sensación de decepción conmigo mismo, ya que sufro muchísimo en el agua, termino reventado, y veo como gente mucho mayor que yo se pasan largos y largos nadando sin apenas esfuerzo. Esto tiene que tener algún truco que yo todavía no he averiguado. Pero soy muy cabezota y no ceso en mi empeño de mejorar.

El día 31 de Julio de 2014, un día antes de mi 38 cumpleaños, decido hacer una prueba de Triathlon Sprint (750m + 20Km + 5Km) para evaluar la situación y tener un punto de partida mas real. Completo estos 3 entrenamientos en una misma tarde:

  • Piscina: 400m: 32m 13s
  • Bici: 14.17km D+160m: 41m 46s
  • Correr: 6.8km D+100m: 35m 40s

Lo que a grosso modo equivale a decir que físicamente me falta poco para terminar un Triathlon Sprint. Este día es anímicamente muy importante para mi porque marca un punto de inflexión. Básicamente me anima a pensar que si no llego para el Triathlon Olimpico (1.5 + 40Km + 10Km), al menos podré competir en el Sprint. Pero el Sprint no es lo que esta en mi cabeza, en mi cabeza esta terminar un Olimpico y para mi no conseguirlo será un fracaso total y absoluto. Pero aún faltan muchos meses y tenemos tiempo. Es momento de entrenar semana a semana sin agobios y esperar que los resultados vayan llegando a medida que pasen las semanas. Cuando falten pocos meses para la competición ya llegará el momento de tomar decisiones. De momento a entrenar a tope y a dar lo mejor de mi mismo en cada entrenamiento.

El 20 de Agosto de 2014 retomo los entrenos con Marc tras su vuelta de vacaciones, y a partír de ese día, cada semana hacemos 30 minutos de entreno en la piscina climatizada de 25m que hay dentro del Club Natació, desde las 6.45h hasta las 7.15am Así hasta completar las 5 horas de entrenamiento contratadas.

Cada 25m tengo que parar a coger aire, pero poco a poco voy mejorando mi técnica, que es mi objetivo principal (nadar eficiente = cansarse menos). Intento absorver como una esponja toda la información que me explica Marc, no paro de preguntarle mis dudas. Cuando llego casa, lo anoto todo en una libreta para que no se me olvide nada. Estas son las principales anotaciones que guardo de sus clases:

  • Mover pies rapido, patada desde la cadera, intentando que se desplacen por el agua en linea recta, no en zigzag
  • Mantener el culo arriba, que no se hunda para mejorar la hidrodinámica
  • Marcar dedo por la axila para sacar el codo bien arriba y extender el brazo al máximo (exagerar) cuando la mano entra en contacto con el agua
  • Juntar los dedos de las manos, que no se abran, pero sin apretar
  • Flexionar el codo dentro del agua para impulsar, no es una polea recta, sensación de avanzar por el agua como si fueras abrazando el agua y te fueras agarrando a ella para impulsarte.
  • El tronco (los hombros) debe hacer un balanceo de lado a lado, no puede ir completamente horizontal

El día 26 de Noviembre de 2015 hago el último entreno con Marc, nadando 600m. He mejorado bastante mi técnica, ya soy capaz de cruzar la piscina de 25m en solo 16 brazadas, aunque todavía soy incapaz de nadar mas de 50m sin parar a coger aire. Como último ejercicio del curso me pide nadar 100m seguidos, sin parar, es decir, hacer 4 piscinas de 25m. Nunca antes lo había hecho. De hecho, el primer entreno poco mas muero ahogado tras 75m. Dudo de mis posibilidades y así se lo hago saber, pero me anima diciendo que algún día tenía que ser el primero.

Así que decido poner en práctica todos los consejos que he aprendido durante los últimos 4 meses: nadar largo, codo alto, culo fuera del agua, patadas desde la cadera, y buscar sensaciones... Aunque justito de fuerzas, consigo completar mis primeros 100m en piscina. No os podéis imaginar la alegría que sentí en ese momento. Aunque le reconozco a Marc abiertamente que nadar es mucho mas duro de lo que imaginaba, y siento admiración por las personas capaces de nadar varios Km en mar abierto sin descansar, en esos momentos me parece un hito prácticamente imposible de conseguir.

Tras terminar los entrenos con Marc, me hago socio del Club Natació Terrassa, y contrato el pack que me permite hacer uso de las instalaciones por 21€/mes, en horario restringido de 6 a 13h de Lunes a Viernes. Poder ir por la tarde y fines de semana casi triplica el coste, y mi bolsillo no da para mas. Una vez contratado, comienzo a entrenar en la piscina Olímpica de 50m reinaugurada tras la reforma.

Decido ir 2 días por semana, Miercoles y Viernes, de 6.15am hasta las 7am. No tengo otra opción, pues el resto de la mañana estoy en el trabajo. Los horarios son malos, me toca madrugar mucho, pero el día cunde el doble, porque una vez entrenado, tengo la tarde libre para mi. Le pillo el gusto a este horario, y cada día que pasa me levanto mas a gusto y con mas ganas de ir a entrenar. Son unas instalaciones espectaculares como veréis en esta foto.

Nadar en la piscina de 50m me exige el doble de esfuerzo que en la piscina de 25m donde hice el cursillo de natación. Los primeros días son duros, la piscina se hace larga, parece que no termina nunca. Pero poco a poco me habituo a la distancia.

Durante los siguientes meses sigo haciendo entrenos semanales de bici, run y gimnasio. Mi plan es entrenar entre 6 o 7 días por semana, distribuyendo los entrenos semanales de esta manera:

  • 2 sesiones de piscina (2Km aprox)
  • 2 sesiones de running (20Km aprox)
  • 2 sesión de MTB (50Km aprox)
  • 1/2 sesiones de gimnasio

¿Será suficiente para terminar un triathlon? No es una preparación muy intensa para preparar este tipo de pruebas, pero si muy larga, por lo que debería tener el cuerpo bastante habituado a este ritmo de entrenamientos semanales, y espero que sea suficiente. Me gustaría tener mas tiempo libre para doblar entrenos y poder prepararlo mejor, pero trabajando 9 horas al día, y manteniendo una casa yo solo (limpieza, compra, cocina, etc), no hay tiempo para mas. Aún así, la intención es ir incrementando las distancias hasta alcanzar los 3Km en piscina, 30Km run, y 70Km bici, semanalmente. Las últimas semanas intentaremos doblar algunos entrenos para acostumbrar al cuerpo a las transiciones. Se que nos esperan unos meses muy duros, pero confío a muerte en este planning, y es con el que espero bautizarme en el mundillo de los triatletas.

Durante el final del año 2014 voy combinando mis entrenamientos con algunos retos personales que tenía pendientes:

El 9 de Agosto, saliendo desde la villa de mis padres, en el Camino de la Fileta (Castellón), hago corriendo la subida al desierto de las Palmas por la carretera CV-147 pasando por la Magdalena, bajada por la parte de Benicassim, y regreso por la playa del Torreón, aeroclub, y de nuevo camino la Fileta, hasta regresar a la villa de mis padres. Distancia total: 32.85Km. Desnivel d+450m. Tiempo total (toma de fotos incluida): 4h 27m 33s.

El 9 de Septiembre de 2014 completo una ruta circular en MTB partiendo desde Benafigos hasta Vistabella (Castellón), y desde Vistabella dirección Adzeneta, desviando a la izquierda justo antes de entrar en el pueblo, para tomar la carretera que sube de nuevo a Benafigos. 48.62Km D+1270m en 3h 08m 45s. Montaña de la buena, con mucho desnivel para lo que estoy acostumbrado en la MTB, pero las piernas respondieron bien. Una ruta preciosa que nada tiene que envidiar a las mejores etapas de montaña de la vuelta ciclista a España.

El 11 de Septiembre de 2014, solo 48 horas después de la ruta en MTB, hago corriendo por asfalto el recorrido que hay desde Benafigos hasta Vistabella y regreso hasta Benafigos. 36.51Km D+855m / 5h 49m 12s. Feliz y contento de haber conseguido bajar de las 6 horas, que era el objetivo marcado. Muy bien de piernas, sin ninguna molestia al terminar, mas que una ampolla previsible en el dedo meñique del pie izquierdo que ya está curada. Disfrutando al máximo de las montañas donde mis abuelos maternos nacieron y vivieron gran parte de sus vidas. Un santuario para cualquier deportista amante de la naturaleza!

El 4 de Octubre de 2014 termino la primera edición de la Ultra-Trail Drac Valles en 9h 43m 09s, tiempo que tardo en completar los 50Km D+2000, por senderos estrechos con vegetación espesa, arboles caídos, túneles de vegetación completamente cerrados donde no entraba la luz del sol, ramas de los arboles a poca altura, zarzas, telarañas, terreno irregular con piedras sueltas, regueros de agua por las lluvias, barro, y continuas subidas y bajadas de varios kilómetros con muchísimo desnivel.

Ya en previsión del incremento de carga de trabajo que voy a hacer durante 2015 hasta que llegue la fecha señalada para el Triathlon, el 23 de Diciembre de 2014 realizo de manera voluntaria una prueba de esfuerzo en Servicio de Medicina Deportiva del Hospital Provincial de Castellón para determinar mi capacidad física para realizar deportes extremos. Hay muchos casos de muertes súbitas en Triatletas populares que podrían haberse evitado realizando esta sencilla prueba. Tras hacer la prueba en una bici estática, el Doctor Joaquin Montoliu emite certificado médico que acredita que tengo un estado de salud bueno y que soy APTO para la práctica deportiva del Triathlon.

Según el informe médico, parto de una buena base aeróbica y anaeróbica, y tengo margen de mejora entrenando a las pulsaciones correctas. La recomendación médica para mis cualidades físicas es entrenar aeróbico entre 145 y 155 ppm. Mi nivel de lactato máximo apareció a los 20 minutos mientras desarrollaba una potencia máxima de 3.3W/Kg.

Quedamos en repetir la prueba a finales de 2015 para estudiar la evolución y ver si los entrenamientos durante este año se han hecho correctamente.

En mi vida deportiva, del mismo modo que en mi vida académica o laboral, para obtener resultados siempre he intentado elegir objetivos que me motiven lo suficiente, y tras la elección del objetivo, realizar una buena planificación para alcanzarlo. Después de la planificación, la motivación para conseguir el objetivo es esencial, ya que resulta clave para condicionar el sacrificio, la dedicación, y el esfuerzo realizado hasta conseguirlo.

Empezamos el año 2015 con un gran objetivo deportivo en mente: ser finisher del Skoda Triathlon Series 2015, el próximo 5 de Julio 2015 en Castellón.

Pagina oficial del Triathlon Skoda Castellon

¿Por que el Triathlon? Porque es la modalidad deportiva que mas me motiva hoy en día. Combina tres deportes que me fascinan. La bici, con la que hice mis pinitos ya con 14 años por el Desierto de las Palmas, San Juan de Moro, Villafames, Costur, y Cabanes. Le cogí miedo después del accidente frontal contra un coche y (por suerte) solo romperme 2 dedos de mi mano izquierda, pero tiempo he tenido de reflexionar sobre lo ocurrido. El running lo llevo practicando desde los 18, cuando dejé el Baloncesto, es de lejos la prueba que llevo mejor preparada. Y la natación, ese deporte que siempre me aconsejaban los médicos para mejorar el estado de salud de mi espalda, llevaba años deseando hacer un curso para aprender la técnica de croll, pero nunca encontraba el momento. Finalmente hace unos meses me decidí a empezar, y ahora mismo ya esta plenamente integrado en mis rutinas semanales de entrenamiento.

¿Modalidad Olimpica o Sprint? Estamos en Enero de 2015 y todavía está por decidir, si modalidad Olimpica o Sprint. Todo dependerá de como llegue a Julio para nadar los 1.5Km en el mar, pues solo estoy entrenando en piscina y soy un absoluto novato nadando en mar abierto, así que no quiero adelantar acontecimientos todavía.

Tengo unas ganas tremendas de llegar al 5 de Julio, empezar a mover las piernas y a sentir como funciona mi cuerpo durante la carrera. Si el entrenamiento ha sido bueno, sé que voy a disfrutar mucho en carrera, lo voy a pasar muy bien. Espero que todo salga según lo esperado!

El 23 de Marzo quedo con Nestor (mi podólogo de confianza), en su clínica, para llevarle las plantillas de running que él mismo me hizo hace un año, para que les pegue un repaso, ya que las rodillas empiezan a doler mas de la cuenta y siempre que ocurre esto es bien por degradación de las plantillas o bien por degradación de las zapatillas. No quiero arriesgarme a sufrir otra tendinitis como la que sufrí preparando el Maraton hace algo mas de un año, así que lo primero es reparar las plantillas y ver si con esto resolvemos el problema. Así quedaron las plantillas tras el arreglo.

El arreglo de las plantillas mejora la situación, pero no quiero riesgos de ningún tipo (lo pasé muy mal con la tendinitis preparando mi primer Maratón) y a poco mas de 1 mes para el Triathlon decido renovar el calzado. El día 24 de Mayo de 2015 compro en Atmosfera Running Castellon, mi tienda de confianza para calzado deportivo, estas ASICS Gel-Nimbus 16 para correr en asfalto.

He corrido con otras muchas marcas como Brooks, Mizuno, y Nike, pero mis piés no terminan de adaptarse a los modelos de zapatilla de estos fabricantes. Las ASICS son las unicas que tratan mis pies con total delicadeza: ni roces, ni ampollas, ni callos, ni molestia de ningún tipo, desde el primer entrenamiento. No quiero decir con esto que las otras marcas sean malas, solo que un mismo modelo de zapatilla no puede adaptarse bien a todos los tipos de pie, y en mi caso concreto, parece que ASICS lo acierta mejor que nadie con mis pies.

El nivel de los entrenamientos se intensifica y con él la tendinitis de rodilla parece querer hacerse la protagonista. Sin embargo, unas sesiones de fisioterapia caseras hacen milagros. ¿El truco? Hacer largas sesiones de estiramientos tanto para calentar como para enfriar, de las que duelen, hacer un cyriax de la fascia lata (masaje transverso profundo), sesiones de frio con bolsa de hielo y Cryogel Sport, y sesiones en la ducha combinando chorros de agua fria/caliente cada 10 segundos. Con esto conseguimos recuperar perfectamente las rodillas, hasta el punto de no sentir ningún dolor. Como no quisiera que una maldita tendinitis se llevara por delante todas las ilusiones que he puesto en esta prueba, decido continuar con este tratamiento diariamente hasta el mismo día del Triathlon.

El día 6 de Junio de 2015, exactamente a falta de 30 días para la prueba, me armo de valor y decido inscribirme en el Triathlon Skoda Castellon 2015, en modalidad Olimpica individual. Tocará nadar 1.5Km en mar abierto, rodar 40Km en bici, y correr 10Km. Acojone es poco lo que siento, pero he entrenado muy duro para esto y ya no hay vuelta atrás!

Hace 5 años que empezó a gestarse en mi cabeza la palabra Triathlon. Llevo 1 año de preparación específica para esta prueba: mas de 100km nadando en piscina, mas de 3000km con la BTT por segmentos mixtos de montaña y asfalto, mas de 1000km corriendo por asfalto, y algunos km mas de trekking en montaña con bastante desnivel. Estamos a 1 mes de llegar al gran día. Emociones a flor de piel, y unas ganas infinitas de estar frente al mar Mediterraneo, y escuchar el disparo de salida. A partir de ese momento el trabajo estará hecho y solo quedará una cosa: disfrutar!

A falta de 3 semanas os lo digo con toda sinceridad: estoy a-co-jo-na-do con los 1500m del segmento de mar. A pesar de llevar casi 1 año entrenando en piscina, nunca he nadado en mar abierto, mi estreno será ni mas ni menos que el día de la prueba. Voy a ir sin neopreno (valen una pasta y no sé si volveré a usarlo) lo que empeora mi flotabilidad en el agua y dificulta un poco mas las cosas. En el mar hay corrientes, olas, algas, en ocasiones medusas. Los tragos de agua salada no saben nada bien. Habrá gente a montones dando golpes y patadas sin otra preocupación que llegar antes que los demás.

Si, en la piscina he mejorado mucho desde que empece a nadar hace 1 año: el primer día fui incapaz de nadar 50m seguidos y ahora termino 1500m en algo menos de 40 minutos. Pero los entendidos en la materia dicen que para terminar bien el segmento de mar hay que doblar la distancia en piscina con holgura. No habrá tiempo ya para mejorar mucho mas.

Es un segmento para ir muy tranquilo, así que en la salida me situaré en la cola del pelotón. Aprovechando que el circuito son dos vueltas de ida y vuelta, mar a dentro nadaré intentando no tragar mucha agua, y mar a fuera nadaré intentando aprovechar las olas del mar para reducir el esfuerzo al mínimo.

En el agua es donde se producen el mayor numero de abandonos en el triathlon. Muchos describen la salida de los peores nadadores como si se tratara del desembarco de Normandia. Sinceramente, le tengo mucho respeto a este segmento, por no decir incluso que algo miedo.

El objetivo es salir del agua, como sea, a poder ser con vida. Si lo consigo sé que mis opciones de completar con éxito mi primer triathlon aumentarán considerablemente. Aunque aún faltará rodar 40Km en bici y correr 10Km. Todavía mucho para levantar los brazos bajo el arco de llegada!

El circuito en bici parte del planetario en dirección al Grao. En la rotonda del Casino gira hacia la avd del Mar, dirección a Castellón, y ya en linea recta, hasta llegar a la rotonda del Carrefour, donde da media vuelta y regresa hasta el punto de partida. Se dan 4 vueltas a este circuito, 40Km en total.

En este segmento tendré una ligera desventaja respecto al resto de triatletas, por el hecho de participar con una mountain bike, bicicleta que según las bases de la organización está permitida para participar en la competición, pero que obviamente tiene sus limitaciones mecánicas (mayor peso, mayor superficie de rozamiento de las ruedas con el suelo, por tanto menor velocidad) y aerodinámicas (la posición en la bicicleta no favorece en absoluto rodar a gran velocidad, mucho menos si hubiera viento frontal). No obstante, me da igual el tiempo, mi objetivo es terminar, no debo olvidarme de esto!

Cuando empecé a preparar este segmento hace 1 año ya tenía cierto rodaje con la bici, aunque nunca había entrenado de manera regular esta disciplina. Tampoco he competido nunca en pruebas ciclistas, así que será el segundo estreno en un solo día. Desconozco las sensaciones de rodar en pelotón, aunque en esta ocasión esto me preocupa realmente poco, pues creo que será imposible seguir con la MTB a cualquier bici de carretera. Probablemente rodaremos solos durante los 40Km y los mas rápidos nos doblarán. Tampoco quiero cegarme a seguir la rueda de nadie, solo quiero terminar. No te olvides de esto, Ivan!

Para preparar esta prueba venimos haciendo 2 entrenos semanales durante el último año, con etapas de entre 30 y 50km por segmentos mixtos de asfalto y montaña, con desniveles de entre D+300 y los D+1200m. El triathlon circula exclusivamente por asfalto y es prácticamente llano, así que la preparación debería ser suficiente para terminar este segmento sin sufrir demasiado.

El plan es rodar muy suave los primeros km para acostumbrar a las piernas a pedalear tras el segmento de natación, y cuando empiece a sentirme cómodo, fijar una cadencia de pedaleo viva que no me desgaste mucho las piernas y que pueda mantener con comodidad. Los últimos km volveremos a aflojar para que las piernas no lleguen demasiado cargadas al último segmento.

Cuando bajemos de la bici estaremos mas cerca del objetivo. Solo nos quedará correr 10Km y podremos levantar los brazos bajo el arco de llegada!

La carrera a pié se realiza en un circuito de 2.5km que parte del planetario y va por el paseo marítimo paralelo a la playa dirección hacia el aeroclub. Un poco antes de llegar al final del pinar da media vuelta, y regresa hasta el punto de partida. Son 4 vueltas al circuito, 10Km en total.

Pudiera parecer que se trata mi segmento mas cómodo, pues corriendo ya he terminado carreras populares mucho mas duras. Pero no se muy bien como responderán mis piernas tras el palizón que llevaremos acumulado. Es un recorrido que conozco a la perfección pues entreno allí muchos días. Suficiente para saber que la humedad en aquella zona es BESTIAL mas el "caloret" que puede hacer un 5 de Julio a las 10.30h (hora en la que calculo que empezaré a correr), aquello se puede convertir en el PUTO INFIERNO. Son dos factores que habrá que tener muy en cuenta durante el segmento de la bici, para hidratar al máximo antes de empezar a correr.

Para mantener mi nivel como runner sin descuidar las otras modalidades del triathlon he tenido que reducir el numero de entrenamientos semanales de carrera continua de 4/5 a solamente 2, intentando que todos ellos fueran tiradas de mas de 11km con unos 150 metros de desnivel por entrenamiento. Seguramente estaré lejos de mis mejores marcas en un 10K, pero estamos preparados para aguantar bien los 10K manteniendo muy bajas las pulsaciones, lo cual, si llegamos con vida a este segmento, dará cierta tranquilidad.

El plan es empezar a correr con pasos muy cortos durante los primeros 5/10 minutos, hasta que la musculatura de las piernas, que vendrá bastante congestionada durante el segmento de bici, se adapte a correr. A partir de ese momento variar el ritmo según lo que me pida el cuerpo. Si voy mal tocará mantener el ritmo bajo y sufrir. Si voy bien, nos dejaremos llevar, sería lo deseable, terminar disfrutando y con una sonrisa de oreja a oreja!

Sea como sea, en los últimos km seguro que nuestra cabeza se inundará de recuerdos que nos harán saltar mas de una lágrima: decenas de madrugones invernales a las 5am para meterte en la piscina cuando en la calle estábamos bajo cero, y después pasar todo el día en el trabajo. Salidas con la bici los Sábados por la mañana en invierno con un frio que pelaba, para regresar a medio día, con todas las tareas del hogar pendientes de empezar, la compra semanal sin hacer, la casa sin barrer... Decenas de salidas nocturnas para correr, cuando el sol se ponía a las 17.30h y todavía no habíamos ni salido del trabajo. Horas y horas en el gimnasio haciendo lo que para mi era la jornada de descanso activo. Esto ha sido muy duro señores, muy duro. Pero el placer de conseguirlo debe ser tan grande que solo imaginar ese momento cruzando bajo el arco de meta me pone tan contento que me hace sonreír mientras escribo esta frase. Solo por esto ya ha merecido la pena todo el esfuerzo!

Como a mi me gusta, retos difíciles y sin ayuda de nadie, yo contra mi mismo, mi cuerpo contra mi mente, cuando la cosa se pone épica es cuando mas me motivo. Vamos a por ello, por mis santos cojones! Vamos A MUERTE, Iván!

El 1 de Julio (a 5 días del Triathlon), y tras un año de intensa preparación empiezo a sentir los nervios típicos de cualquier estreno en una competición. La preparación ha sido muy larga, pero nunca te sientes lo suficientemente preparado para afrontar un reto de esta embergadura. Por una parte pienso que no he podido entrenar mas, es todo lo que mi tiempo libre ha dado de sí. Esta parte me da tranquilidad. Por otra parte pienso que la gente va muy preparada a estas competiciones, y la incertidumbre de no saber si voy a estar a la altura está ahi presente, continuamente. Esa incertidumbre es lo que peor llevo los días antes de la competición.

Sea como sea, la suerte esta prácticamente echada. El próximo Domingo 5 de Julio a las 8 de la mañana, allí estaremos para intentarlo con todas nuestras fuerzas. Con un solo objetivo en mente: ser finisher del triathlon Skoda Castellón, 2015. Después del coñazo que os he dado durante todo este tiempo estoy seguro que mas de uno estará deseando que termine todo esto. Os aseguro que yo también. En 5 días el desenlace!

Y por fin llega el día esperado, 5 de Julio de 2015. El despertador estaba puesto para sonar a las 5.55h pero 20 minutos antes ya no podía dormir, los nervios se me comían vivo. Me levanté y desayune una pechuga de pollo, un vaso de soja con cereales, y un platano. Subí al cuarto de baño a asearme un poco. Entre en la habitación donde tenía todo el material preparado y sople fuerte, hoy era el dia y esto iba en serio. Vi el trimono, y con máximo respeto a lo que me esperaba durante las siguientes horas, me lo puse con ciertos nervios, pues sabía que era el día del todo o nada. Cargue la mochila con todo el material necesario que ya había dejado cuidadosamente preparado la noche anterior. A las 6.40h cogí la bici, la mochila, y me fuí pedaleando hasta el planetario del Grao de Castellón, donde estaba la linea de salida.

Llegué 15 minutos mas tarde, sobre las 7 de la mañana. Allí todo era nuevo para mi, no tenía ni idea de lo que tenía que hacer al llegar. Enseguida uno de los organizadores me orientó hacia la entrada al box de las bicis. Presenté el dni, me revisaron los frenos y los tapones del manillar, y me dieron el ok a la mecanica de mi MTB. Ya en el box colgue la bici en la barra, y dejé en el suelo, a la derecha de mi bici todo el material que iba a usar para la T1 y la T2, quedándome solo con el gorro de natación, los tapones de silicona y las gafas de natación. En ese momento hice la foto de rigor para inmortalizar el momento.

Sabía que era la última foto hasta que mi participación en la prueba finalizara, con el resultado que fuera. Metí mi movil en la mochila y caminé descalzo hacia la salida del box de las bicis. En ese trayecto de salida del box empecé a mirar alucinado las bicis con las que participaban el resto de participantes. Ni una sola MTB, todo bicis de carretera, muchas de ellas verdaderos "aviones" de gama alta, con cuadros aero de fibra de carbono, ruedas de perfil alto, pedales automáticos, etc. etc. etc... Empecé a reirme, pero que haces aquí con una MTB de 700 euros que pesa casi mas que yo, Ivan!!

Dejé la mochila en el guardarropas y de allí me fuí directo a la playa, donde en poco mas de media hora daría comienzo el segmento de natación. De camino encontré a mi padre que acababa de llegar. Estuve hablando con el un momento, lo que me tranquilizó un poco, pues estaba bastante nervioso. Tras hablar con mi padre seguí andando por la arena de la playa hasta la orilla del mar, para comprobar la temperatura del agua. Era una temperatura muy agradable, de unos 27º, ninguna sensación de frio desde el mismo momento en el que metí los pies en el agua. Eso era perfecto para mi, acostumbrado a nadar en piscina climatizada, el frio en el agua lo llevaba bastante mal. Ademas había otra buena noticia. El agua estaba muy tranquila. Me preocupaba el asunto porque unos días antes hubo un fuerte temporal y el día anterior la bandera de cruz roja ondeaba amarilla porque había bastante oleaje. Entré en el agua para acostumbrar al cuerpo a la temperatura, pero se estaba mejor dentro del agua que fuera, así que me quedé dentro del agua unos minutos mientras miraba como unas barcas colocaban las boyas que delimitaban el recorrido acuático y me hacía a la idea de lo que me esperaba. Salí del agua para hacer unos estiramientos de brazos y piernas, y en seguida nos llamaron para entrar en la zona de salida. Me situé en la parte de atrás, a la izquierda, pues quería evitar los golpes y las patadas a toda costa mientras mantenía referencia visual con el resto de nadadores (respiro siempre por la derecha). A las 8 en punto dieron salida a la prueba.

Empecé a nadar muy tranquilo, tanto que pronto me di cuenta que me estaba quedando solo en el agua. Pero no me importaba nada, sabía que el agua era el segmento mas complicado para mi, por mi poca experiencia como nadador. Así que me lo tomé con muchisima calma. Nadé tranquilo durante la primera vuelta, nadando largo, tratando de deslizar el máximo por el agua minimizando el número de brazadas, buscando sensaciones en todo momento, manteniendo las pulsaciones muy bajas y tirando exclusivamente de dorsal y de triceps, sin apenas usar las piernas para propulsar, mas que para equilibrar mi cuerpo en el agua, ya que quería que mis piernas llegaran frescas al segmento de bici. Antes de completar la primera vuelta, tratando de orientarme, abrí la boca en exceso mientras cogía aire y metí un buen trago de agua salada. Fue un momento bastante desagradable, una mezcla de agua salada con sabor a aceite y petroleo. Tuve que escupir el agua como pude mientras seguía nadando. Era un aviso, no se podían cometer mas errores o lo pasaríamos mal. Así que intenté concentrarme en no volver a cometer ese mismo error. Salí a la arena completando la primera vuelta al circuito, y volví a entrar en el agua para iniciar la segunda vuelta. Mis piernas estaban intactas, mi respiración pausada, y mis brazos sin dolor alguno, así que empecé a nadar de nuevo buscando las mismas sensaciones con las que había completado la primera vuelta. Hacía la mitad de la segunda vuelta me dí cuenta que aquel segmento lo tenía mas que hecho, y solté alguna lágrima recordando mi primer entreno con Marc en el Club Natació de Terrassa, cuando fui incapaz de hacer 100m seguidos sin ahogarme. Estaba aún con las gafas de natación puestas, así que las lágrimas me molestaban, con lo que me centré en seguir nadando hasta salir del agua. Y eso hice. Lo conseguí en 48m 15s. Un tiempo muy discreto, casi 10 minutos mas de lo que tardo en hacer esa misma distancia en piscina hoy en día. Pero me da igual, con una sonrisa de oreja a oreja por haber salido con vida del segmento que mas miedo me daba, el agua. Físicamente me siento intacto, no me duele nada! Así que empiezo a pensar que quizás no estaba tan loco cuando me planteé hacer el Olimpico, pues solo quedan dos pruebas en las que soy mucho mejor que nadando, aunque todavia podrían salir muchas cosas mal. Por lo que me centro en continuar con el siguiente segmento sin dejarme llevar por las emociones.

Voy corriendo hasta el box de las bicis, y no me cuesta mucho encontrar la mía, pues el resto de las bicis ya han salido antes que yo. Tal y como venía ensayando mentalmente desde que salí del agua, me pongo el casco, el dorsal en la parte de atrás, me pongo calcetines y zapatillas de runner, ato los cordones, me tomo un PowerGel y bebo un trago de agua. Mi padre en ese momento esta en frente de mi animándome para salir rápido y llamando a mi madre por teléfono para decirle que ya he salido del agua y estoy a punto de salir con la bici. Yo le doy las grácias, cojo mi Trek y salgo corriendo por el largo pasillo hasta la zona donde el juez de carrera me da permiso para montar en la bici y iniciar mi segundo segmento. Los distintos voluntarios me guían por el circuito que sale hasta la carretera del Pinar, y de allí hasta la Avd. del Mar, dirección a Castellón. A partir de ahi empiezo a pedalear muy a gusto, pero no paran de adelantarme bicis "avión" a toda velocidad. No quiero obsesionarme con seguir a nadie, así que voy a mi ritmo, en torno a los 26Km/h de camino hacia Castellón (en ligera subida), donde doy media vuelta para regresar hacía el Grao a unos 30Km/h de media (en ligera bajada). Voy dando sorbos a los 2 bidones de liquido que llevo, uno con zumo de naranja y otro con Aquarius y Isostar Long Distance Energy, en previsión de las altas temperaturas que dan durante el segmento de run. Y así termino la primera vuelta. De nuevo veo a mi padre animándome en una esquina del circuito, y me pongo a sonreir porque en ese momento estoy ya disfrutándo como un niño con un juguete nuevo. De nuevo hacia Castellón, doy la segunda vuelta, y aunque adelanto a algún ciclista rezagado, no paran de adelantarme grupos de ciclístas rodándo a casi 40Km/h. Pienso en que hay gente muy "pro" en estas competiciones, pero no me importa lo mas minimo. Yo sigo a lo mio, y hoy lo mio es terminar! En la tercera vuelta de camino hacia el Grao, tras beber mas de 1L de liquido de los bidones, no aguanto mas las ganas de orinar, y me tengo que bajar de la bici, desabrochar el trimono como puedo (cremallera en la espalda), orino, vuelvo a abrochar el trimono, cedo el paso a otro grupo de ciclistas que me adelantan (otra vez) en ese preciso intante, y ya por fin monto de nuevo en mi bici para continuar. Siento un alivio tremendo tras orinar, y retomo el segmento de bici con mucha fuerza, y es que las piernas me van solas, no siento dolor alguno, se notan los entrenos por montaña con la MTB! Al terminar la tercera vuelta ya no veo a mi padre, imagino que ya debe haber ido a la zona de meta donde estarán entrando los primeros clasificados, y me alegro porque así se entretendrá un buen rato mientras yo completo el circuito de bici. Inicio la cuarta vuelta, ya rodando prácticamente solo pero manteniendo el mismo ritmo que en las vueltas anteriores y sin apenas sentir dolor en las piernas. Completo el circuito de bici, tiempo total: 1h 19m 26s (28.7km/h).

Corriendo de nuevo llego hasta el box de las bicis. En ese momento veo un montón de bicis aparcadas en el box, casi todo el mundo ha terminado ya con la bici y está corriendo el último segmento. Llego por el largo pasillo de bicis hasta el lugar donde debo dejar mi Trek, la cuelgo del sillín, me quito el casco, giro el dorsal de detrás a delante, me tomo otro PowerGel, bebo otro trago de agua, le pego dos bocados a un plátano que he traído de casa, me pongo la gorra, y a correr 10K! Los jueces me guían por la transición hasta el paseo marítimo, donde inicio la primera de las cuatro vueltas al circuito de carrera a pie. La primera vuelta corro muy reservado, confío en mis posibilidades en este segmento mas que en los dos anteriores, pero se supone que vengo cansado, estamos a mas de 30 grados, con una humedad relativa superior al 72%, y esas condiciones son duras para la práctica de cualquier deporte al aire libre. Aún asi, me siento muy comodo corriendo y completo la primera vuelta sin apretar en absoluto, voy tranquilo, adelantándo a gente sin mucho esfuerzo. Al completar la primera vuelta veo a mi padre en la linea de meta, me emociono un montón y le digo gritando que solo faltan 3 vueltas y ya lo tengo hecho! Inicio la segunda vuelta, en la que cojo un botellín de agua y de camino me lo voy echando por encima para enfriar mi cuerpo. La temperatura es ya infernal, pero aún así me siento cómodo, y completo la segunda vuelta incluso mas rápido que la primera. Al terminar la segunda vuelta veo a mi hermano que está con mi padre en la linea de meta, animando. Me alegro un montón de verle allí animando, y le digo gritando y sonriendo que solo faltan 2 vueltas! Sigo corriendo hasta llegar de nuevo al habituallamiento liquido. Vuelvo a coger botellín de agua y lo dosifico para ir echandolo en mi cabeza, en mi pecho, en mis piernas, a medida que avanzo. Son las 10.30h pasadas y el calor empieza a apretar. Veo a mucha gente sufriendo, muchos ya van andando, algunos con la cara desencajada. Yo sigo corriendo, a un ritmo comodo, y completo la tercera vuelta en un tiempo similar al de la segunda. Mi hermano me anima gritando mientras me graba un video con el movil. Inicio la cuarta y última vuelta. Veo que esto se termina y yo voy bien, así que decido dejarme llevar, quito el freno de mano y empiezo a correr con mas fuerza, a un ritmo mas alto, soltando zancadas largas y respirando bien, adelanto a una decena de corredores sin ninguna dificultad, lo que a falta de 600 metros me anima a seguir incrementando el ritmo. En los últimos 40 metros esprinto y consigo adelantar a 3 corredores mas. Finalmente paso por el arco de meta completando los 10K del segmento de run en un tiempo total de 48m 11s. Mi mejor marca en un 10K la hice hace 1 año con un tiempo de 43m 59s, por lo que considerando el esfuerzo previo para terminar los otros dos segmentos, para mi es un tiempazo!

Levanto los brazos como si fuera el vencedor de la prueba. Allí nadie debe entender nada, pero ese momento es mio y quiero vivirlo intensamente, a mi manera. En esos instantes de absoluto subidón de adrenalina repaso mentalmente a todas las personas que de alguna manera me habéis ayudado y animado a completar este reto: médicos, gimnasios, piscinas, entrenadores, mecánicos, amigos, redes sociales, y por supuesto a mis padres, a mi hermano y a mi novia. En mi mente estábais todos vosotros en ese momento tan mágico que capturó el fotografo con tanto acierto.

Sin dejar de sonreir, tras cruzar el arco de meta, y sin salir de la zona acotada para los corredores que llegan a meta, mi padre y mi hermano se acercan por la valla para felicitarme, y me preguntan si estoy muy cansado. Les digo que me encuentro fenomenal, puedo hablar con ellos sin problemas respiratorios, el corazón me late a menos de 130ppm, mi cuerpo ha reaccionado mejor de lo esperado, estoy grátamente sorprendido, me esperaba llegar completamente rebentado pero la realidad es que me encuentro muy bien! Tras salir de la zona vallada, me despido de mi padre y mi hermano, y mientras ellos se van a buscar el coche para regresar a casa, yo voy a buscar mi bici al box y la mochila al guardarropas.

Recojo la bici y la mochila y regreso hasta mi casa pedaleando, sonriente, incapaz de disimular la alegría que llevo encima y pensando que la gente que me vean por ahi sonriendo de esa manera encima de una bici pensarán que estoy como una cabra! Jaja, quizás tengan razón! :)

Acabo de hacer realidad un sueño que comenzó hace mas de 5 años cuando ni siquiera tenía una bici con la que competir en la prueba ciclista y era incapaz de nadar 25 metros en una piscina. Entonces la palabra "triathlon" sonaba en mi cabeza como algo lejano pero muy atractivo. Hace 3 años y medio compré mi primera bici MTB y solo hace 12 meses que empecé a nadar. Las piezas han ido encajando poco a poco en su sitio y hoy 5 de Julio de 2015 puedo decir con satisfacción que ese sueño ya es una realidad.

De los 423 triatletas que completaron la distancia Olimpico individual de Triathlon Skoda Castellón 2015, terminé en la posición 394, con un tiempo total de 3h 1m y 44s. A tod@s los triatletas que terminásteis la prueba, mi mas sincera enhorabuena, MÁXIMO respeto para tod@s, del primero al último, y grácias por ser los protagonistas y formar parte de esta experiencia única, irrepetible e inolvidable que hoy viví con todos vosotros. Si todo va bien, volveremos a vernos el año que viene, pero esta vez no os lo voy a poner tan facil como este año! :D

¿Y a partir de ahora que? Habrá nuevos retos, os lo aseguro, y mas dificiles que este, pero de momento nos vamos a tomar unos días de descanso para ir planificando nuevas aventuras. Estad atentos al Facebook que pronto habrá novedades!

Para todos los que como yo sintáis el deseo de entrenar para realizar un Triathlon Olimpico, aquí os dejo los entrenos que realicé durante los últimos 3 meses, por si os sirven a modo de entrenamiento. Mucho animo que merece la pena intentarlo!

    Abril   X   1   btt 43.55km
    Abril   J   2   swm 925m
    Abril   V   3   trk 33.5km
    Abril   S   4   trk 28.5km
    Abril   D   5   descanso
    Abril   L   6   run 8km
    Abril   M   7   gym
    Abril   X   8   swm 900m
    Abril   J   9   gym
    Abril   V   10  swm 1000m
    Abril   S   11  btt 34km
    Abril   D   12  run 11km
    Abril   L   13  gym
    Abril   M   14  descanso
    Abril   X   15  swm 800m
    Abril   J   16  run 11km
    Abril   V   17  swm 1000m
    Abril   S   18  btt 31.5km
    Abril   D   19  run 12.6km
    Abril   L   20  gym
    Abril   M   21  btt 26.9km
    Abril   X   22  swm 1200m
    Abril   J   23  descanso
    Abril   V   24  swm 1100m
    Abril   S   25  btt 53.3km
    Abril   D   26  run 11km
    Abril   L   27  gym
    Abril   M   28  btt 32.47km
    Abril   X   29  gym
    Abril   J   30  run 11km

    Mayo    V   1   swm 1600m
    Mayo    S   2   viaje
    Mayo    D   3   viaje
    Mayo    L   4   viaje
    Mayo    M   5   run 11km
    Mayo    X   6   gym
    Mayo    J   7   btt 36km
    Mayo    V   8   swm 1100m
    Mayo    S   9   trk 15km d+1000
    Mayo    D   10  run 10.85km
    Mayo    L   11  gym
    Mayo    M   12  btt 31.17km
    Mayo    X   13  swm 1100m
    Mayo    J   14  descanso
    Mayo    V   15  swm 1000m
    Mayo    X   16  run 14.60km
    Mayo    D   17  btt 40.34km
    Mayo    L   18  gym
    Mayo    M   19  gym
    Mayo    X   20  swm 1300m
    Mayo    J   21  run 11.70km
    Mayo    V   22  swm 1100m
    Mayo    S   23  swm 1325m + btt 35km
    Mayo    D   24  descanso
    Mayo    L   25  gym
    Mayo    M   26  run 12.85km
    Mayo    X   27  swm 1400m
    Mayo    J   28  btt 37.09km
    Mayo    V   29  swm 1100m
    Mayo    S   30  btt 37.65km
    Mayo    D   31  run 12.18km

    Junio   L   1   gym
    Junio   M   2   descanso
    Junio   X   3   swm 1500m (piscina)
    Junio   J   4   btt 33.30km
    Junio   V   5   swm 1100m (piscina) + gym
    Junio   S   6   btt 36.56km + run 4.70km
    Junio   D   7   run 11.04km
    Junio   L   8   gym
    Junio   M   9   run 12.93km
    Junio   X   10  swm 1600m (piscina)
    Junio   J   11  descanso
    Junio   V   12  swm 1000m (piscina) + gym
    Junio   S   13  btt 44.99km + run 6.84km
    Junio   D   14  run 12.14km
    Junio   L   15  gym
    Junio   M   16  descanso
    Junio   X   17  swm 1700m (piscina) + btt 31.16km
    Junio   J   18  run 10.98km
    Junio   V   19  swm 1000m (piscina) + gym
    Junio   S   20  btt 34km
    Junio   D   21  run 13.46km + swm 800m (mar)
    Junio   L   22  gym
    Junio   M   23  run 13.08km
    Junio   X   24  swm 2000m (piscina)
    Junio   J   25  btt 36.62km
    Junio   V   26  swm 1300m (piscina) + gym
    Junio   S   27  run 16.20km
    Junio   D   28  btt 51.82km + swm 1275m (mar)
    Junio   L   29  descanso
    Junio   M   30  gym

    Julio   X   1   swm 1800m (piscina) + btt 33.62km 
    Julio   J   2   run 12.95km
    Julio   V   3   descanso
    Julio   S   4   swm 400m (piscina) + btt 15.89km + run 2.64km
    Julio   D   5   Triathlon Skoda Castellon 2015 

sábado, 13 de junio de 2015

La generación de resultados y el código fuente del lenguaje m2k2

Introducción

En este post vamos a seguir todos los pasos necesarios para diseñar e implementar un intérprete. Un intérprete es un programa que recibe como entrada un programa en un lenguaje determinado y que produce como salida el resultado de su ejecución. El lenguaje aceptado como entrada por el intérprete es el denominado m2k2, definido formalmente en el primer post.

El diseño del intérprete se estructura en cuatro partes claramente diferenciadas que a su vez configuran la estructura principal de este documento:

  • Análisis léxico
  • Análisis sintáctico
  • Análisis semántico
  • Generación de resultados

En este post se expone la generación de resultados y al final se proporciona el código fuente.

Generación de resultados

El árbol de sintaxis abstracta construido durante la fase de análisis semántico no solo es útil para realizar las comprobaciones semánticas. Este árbol resulta especialmente cómodo para realizar la interpretación. Para ello, cada nodo del AST cuenta con un método interpret que realiza la interpretación de ese nodo y a su vez dispara las interpretaciones oportunas sobre sus nodos hijos. Todo el proceso de interpretación comienza en el programa principal con la llamada:

    AST.interpret()

Obviamente, el AST se interpreta solo cuando la relación de comprobaciones semánticas finaliza con éxito. Se enumera a continuación la interpretación de cada uno de los distintos nodos del AST.

    node_Declaracion.interpret( )

La interpretación de este nodo tiene como efecto lateral la declaración como variables de los lexemas que aparecen en la lista de identificadores.

    node_Asignacion.interpret( )

La interpretación de este nodo calcula el valor que devuelve la interpretación de la expresión y como efecto lateral asigna ese valor al identificador que actúa como receptor de la asignación. En caso de ser necesario, realiza el typecasting.

    node_Expresion.interpret( )

La interpretación de este nodo calcula el valor que devuelve la interpretación de la expresión y imprime ese valor por el stdout.

    node_MasBinario.interpret( )

La interpretación de este nodo devuelve el resultado de sumar la interpretación de sus dos términos.

    node_MenosBinario.interpret( )

La interpretación de este nodo devuelve el resultado de restar la interpretación de sus dos términos.

    node_O.interpret( )

La interpretación de este nodo devuelve el resultado de aplicar el operador O lógico a la interpretación de sus dos términos. La interpretación del nodo se realiza por cortocircuito, lo cual quiere decir que si el primer térmito se evalúa como "true", ya no se interpreta el segundo término. Esto tiene como consecuencia que una expresión como esta:

    >>> 1 | (1/0)
    1

no provoca error de ejecución por división por cero.

    node_Mul.interpret( )

La interpretación de este nodo devuelve el resultado de multiplicar la interpretación de sus dos factores.

    node_Div.interpret( )

La interpretación de este nodo devuelve el resultado de dividir la interpretación de sus dos factores.

    node_Y.interpret( )

La interpretación de este nodo devuelve el resultado de aplicar el operador Y lógico a la interpretación de sus dos factores. La interpretación del nodo se realiza por cortocircuito, lo cual quiere decir que si la interpretación el primer factor devuelve falso como resultado, el segundo factor ya no se evalúa, con lo que una línea como esta,

    >>> 0 & (1/0)
    1

se evalúa sin que se produzca el error de ejecución.

    node_PorCien.interpret( )

La interpretación de este nodo devuelve el resultado de calcular el resto a la interpretación de sus dos factores.

    node_Cmp.interpret( )

La interpretación de este nodo devuelve el resultado de comparar la interpretación de sus dos factores.

    node_Ident.interpret( )

La interpretación de este nodo devuelve el valor que el identificador tiene almacenado en la tabla de símbolos.

    node_NrEnter.interpret( )

La interpretación de este nodo devuelve el valor de la constante entera.

    node_NrReal.interpret( )

La interpretación de este nodo devuelve el valor de la constante real.

    node_MasUnario.interpret( )

La interpretación de este nodo devuelve el resultado de la interpretación del factor, manteniendo su signo.

    node_MenosUnario.interpret( )

La interpretación de este nodo devuelve el resultado de la interpretación del factor, cambiando su signo.

    node_No.interpret( )

La interpretación de este nodo devuelve el resultado de la interpretación del factor con la lógica invertida.

    node_OpTorio.interpret( )

La interpretación de este nodo calcula en primer lugar el resultado de la interpretación de las expresiones 1 y 2 del operatorio. Después de ésto, asigna a la variable muda del operatorio el valor devuelto por la interpretación de la expresión 1 y comienza a iterar sobre la variable muda del operatorio desde el valor devuelto por la interpretación de la expresión 1 mas uno hasta el valor devuelto por la interpretación de la expresión 2, acumulando las interpretaciones parciales del operatorio en una variable temporal res. Al terminar las iteraciones, el operatorio devuelve res como resultado de interpretar el operatorio.

    >>> (+) (i, 1..3, i)
    >>> 6

Al finalizar las iteraciones, la variable muda del operatorio contiene el valor devuelto por la interpretación de la expresión 2 del operatorio si este valor es mayor o igual que el valor devuelto por la interpretación de la expresión 1.

    >>> i
    >>> 3

Si el valor devuelto por la interpretación de la expresión 2 es menor que el valor devuelto por la interpretación de la expresión 1, la variable muda contiene el valor devuelto por la interpretación de la expresión 1.

    >>> (+) (i, 1..0, i)
    >>> 1
    >>> i
    >>> 1

Al igual que ocurre con los nodos relacionados con operaciones lógicas, los operatorios lógicos tambien se interpretan por cortocircuito. En ese caso, al finalizar la interpretación del nodo, la variable muda no contiene el valor máximo del rango sino el valor del rango que ha causado el cortocircuito de la interpretación del operatorio. Por ejemplo:

    >>> (&) (i, -2..2, i)
    >>> 0
    >>> i
    >>> 0

Vemos en el ejemplo anterior como la variable "i" usada como indice del operatorio se ha incrementado desde -2 hasta alcanzar el valor 0. Ese valor 0 ha hecho falsa la expresión (&). Y ahi ha finalizado la evaluación del operatorio, ya no se han evaluado los valores 1 y 2.

Errores de ejecución

La gestión de los errores de ejecución se hace con la misma filosofía con la que se gestionan los errores léxicos, sintácticos y semánticos: en el programa principal se capturan todas las posibles excepciones que se pueden lanzar durante la ejecución de alguna acción dentro del código del intérprete. En concreto se capturan estas tres excepciones:

  1. OverflowError
  2. ZeroDivisionError
  3. ValueError

La excepción "OverflowError" se dispara cuando se desborda un rango al calcular una operación aritmética. Por ejemplo:

    >>> 100000 * 100000
    Execution Error: overflow error

La excepción "ZeroDivisionError" se dispara cuando se produce una división por cero. Por ejemplo:

    >>> 1/(2-2)
    Execution Error: zero division error

La excepción "ValueError" se dispara cuando se utiliza la función atoi o la función atof sobre una cadena de dígitos que excede el rango de representación de los enteros o los reales respectivamente. Por ejemplo:

    >>> 9999999999999999999999
    Execution Error: value error

Con cualquiera de las 3 excepciones anteriores, el programa principal actúa capturando la excepción y mostrando por el stderr el mensaje informativo que corresponda a la excepción capturada, tal como se observa en los ejemplos anteriores. Una vez atendido el error de ejecución, se atiende la siguiente línea del stdin.

El código fuente

Directamente descargable desde github, en este repositorio:

https://github.com/aicastell/m2k2.git

Espero que lo disfrutéis! :)

sábado, 6 de junio de 2015

El analizador semántico del lenguaje m2k2

Introducción

En este post vamos a seguir todos los pasos necesarios para diseñar e implementar un intérprete. Un intérprete es un programa que recibe como entrada un programa en un lenguaje determinado y que produce como salida el resultado de su ejecución. El lenguaje aceptado como entrada por el intérprete es el denominado m2k2, definido formalmente en el primer post.

El diseño del intérprete se estructura en cuatro partes claramente diferenciadas que a su vez configuran la estructura principal de este documento:

  • Análisis léxico
  • Análisis sintáctico
  • Análisis semántico
  • Generación de resultados

En este post se expone el diseño y la implementación del analizador semántico. En el siguiente post hablaremos de la generación de resultados.

Analisis semántico

Determinadas características del lenguaje de programación de entrada no se pueden modelar mediante la gramática con partes derechas regulares (GDPR) del analizador sintáctico, por lo que es necesario que se comprueben en una fase posterior al análisis sintáctico que se conoce como análisis semántico. Un ejemplo de una de estas reglas semánticas que no se pueden modelar mediante la gramática con partes derechas regulares es que en m2k2 los identificadores tienen que estar declarados antes de poder utilizarse.

La tabla de simbolos

La tabla de símbolos se emplea, entre otras cosas, para revisar si un identificador ya ha sido declarado. Así, el uso de la tabla de símbolos puede considerarse como parte del proceso de análisis semántico. En el módulo semantico.py se implementa la clase SymbolTable.

    class SymbolTable:
        def __init__( )

        # gestión de buffer
        def declare( type, id )
        def isdeclared( id)
        def gettype( id )
        def getvalue( id )
        def putvalue( id, v )

        # gestión de silent
        def setsilent( id )
        def setnotsilent( id )
        def issilent( id )
        def clearallsilent( )

La tabla de simbolos es un objeto instanciado de la clase anterior, declarado como global dentro del módulo semantico.py

    symTable = SymbolTable( )

El constructor de esta clase no recibe ningun parámetro de entrada pero internamente crea dos atributos importantes:

    self.buffer={}
    self.silent=[]

El atributo buffer es un diccionario Python que almacena en cada posición buffer[id] una lista con dos valores: el tipo del identificador id y su valor. El diccionario se inicializa a vacio. Los métodos que gestionan el atributo buffer tienen el comportamiento esperado según el nombre de su prototipo. La declaración de un nuevo identificador asigna un valor por defecto 0 a ese identificador si es de tipo entero y 0.0 si es de tipo real. El resto de métodos sirven para comprobar si un identificador esta o no declarado, para obtener el tipo base correspondiente a un identificador, para asignar un valor a un identificador, y para obtener el valor que un identificador tiene asignado. El atributo silent esta relacionado con las comprobaciones semánticas que se hacen sobre el operatorio. Hablaremos mas sobre el atributo "silent" y sobre los métodos que lo gestionan en una sección posterior.

Representación semántica elegida

Como representación semántica se utiliza un árbol de sintaxis abstracta o AST, aprovechando que la GPDR implementada en el analizador sintáctico tiene una cómoda representación como AST. El AST tiene 3 raices posibles:

  • node_Declaracion
  • node_Asignacion
  • node_Expresion
La interpretación de cada raíz en realidad supone realizar una de las tres acciones posibles con el intérprete:

  • La declaración de una lista de variables
  • La asignación del resultado devuelto por una expresión a un identificador
  • La escritura por stdout del resultado devuelto por una expresión

A continuación se muestra la representación gráfica de cada uno de los 3 posibles nodos del AST:

Los nodos node_Asignacio y node_Expresio tienen un campo expr que apunta a una expresión. Esta expresión puede tener una representación tan compleja como la del AST mostrado en este ejemplo:

Esquema de traducción

El análisis semántico no se realiza de forma independiente al análisis sintáctico, sino que ambos se realizan de forma simultánea. El analizador semántico empieza por construir los AST introducidos en la sección anterior. Para ello usa la gramática atribuida que se construye progresivamente en esta sección a medida que se explica la razón de los pasos seguidos.

<Linea> <DeclVar> tkEOL {RETURN <DeclVar>.ast}
<Linea> <Sentencia> tkEOL {RETURN <Sentencia>.ast}

Si la <Linea> se parsea correctamente, se devuelve el AST resultante.

<DeclVar> <Tipo> <ListaIds> { <DeclVar>.ast = node_Declaracion( <Tipo>.t, <ListaIds>.l }
<Tipo> tkTpoEnter { <Tipo>.t = "enter" }
<Tipo> tkTpoReal { <Tipo>.t = "real" }
<ListaIds> {l = [] } tkIdent {l=l+tkIdent.lex} ( tkComa tkIdent {l=l+tkIdent.lex} )*

El método que parsea a <DeclVar> comienza por parsear a <Tipo>. Si el método que parsea a <Tipo> finaliza con éxito, devuelve en la variable sintetizada t el tipo de las variables que se deben declarar. El método que parsea a <DeclVar> continua parseando a <ListaIds>. A medida que se parsea a <ListaIds> se construye una lista l con los lexemas de todos los identificadores que hay que declarar. Si el método que parsea a <ListaIds> finaliza con éxito, el método que parsea a <DeclVar> ya puede construir un node_Declaracion con los atributos sintetizados t y l.

<Sentencia> {IF self.a.cat == "tkIdent" THEN leftIdent = self.a.lex}
<Expresion> {<Asignacion>.isident = <Expresion>.isident }
<Asignacion>
{IF <Asignacion>.isnotempty
THEN Sentencia.ast = node_Asignacion( leftIdent, Asignacion.ast )
ELSE Sentencia.ast = node_Expresion( Expresion.ast ) ENDIF}
<Asignacion> tkAsign { IF <Asignacion>.isident = 0 THEN "Error" ENDIF}
{Asignacion.isnotempty = 1} <Expresion>
<Asignacion> λ {Asignacion.isnotempty = 0}

El método que parsea a <Sentencia> empieza guardando en leftIdent el lexema del token actualmente analizado si este token es un identificador. Después se llama al método que parsea a <Expresion>. Si el parseo de <Expresion> finaliza con éxito, <Expresion> retorna un atributo sintetizado isident que indica si la expresión parseada es o no un identificador. El valor de isident se copia en <Asignacion> mediante su atributo heredado isident, y a continuación se llama al método que parsea a <Asignacion>.

Dentro del método que parsea a <Asignacion> se utiliza el atributo heredado isident para saber si la parte izquierda de la asignación es o no un identificador. En caso de encontrar un tkAsign en la entrada, se genera un error si la parte izquierda de la asignación no es un identificador. El método devuelve cierto en el atributo sintetizado isnotempty si la asignación tiene parte derecha, y falso en caso contrario.

De vuelta en el método que parsea <Sentencia> si el método que parsea <Asignacion> finaliza correctamente, se evalúa el atributo sintetizado isnotempty que ha devuelto la llamada al método que parsea . Si la asignación tiene parte derecha, se construye un node_Asignacion, y si no tiene parte derecha, se trata de una sentencia expresión, por lo que se construye un node_Expresion.

<Expresion> <Termino>
{<Expresion>.isident = <Termino>.isident}
{<Expresion>.ast = <Termino>.ast}
(( tkMas | tkMenos | tkO ) <Termino>
{<Expresion>.isident=0}
{IF tkMas THEN node_MasBinario( <Expresion>.ast, <Termino>.ast ) ELSE
IF tkMenos THEN node_Menos( <Expresion>.ast, <Termino>.ast ) ELSE
IF tkO THEN node_O( <Expresion>.ast, <Termino>.ast ) ENDIF} )*

Lo primero que hace el método que parsea a <Expresion> es llamar al método que parsea a <Termino>. Si el método que parsea a <Termino> retorna con éxito, <Termino> vuelve con dos atributos sintetizados: el atributo isident, que indica si <Termino> es un identificador o no lo es, y el atributo ast, que contiene el AST de ese <Termino>. Esos dos valores promocionan hacia arriba copiandose en la <Expresion>. Si en este punto se lee de la entrada un token tkMas, tkMenos o tkO, entonces se afirma que <Expresion> no es un identificador, y se construye un node_MasBinario, node_Menos o un node_O con lo que hay en <Expresion>.ast hasta ese momento y con el <Termino>.ast recien calculado. Las sucesivas iteraciones de la clausura irán construyendo correctamente las ramas del AST.

<Termino> <Factor>
{<Termino>.isident = <Factor>.isident}
{<Termino>.ast = <Factor>.ast}
( ( tkMul | tkDiv | tkY | tkPorCien | tkCmp ) <Factor>
{<Termino>.isident=0}
{IF tkMul THEN node_Mul( <Termino>.ast, <Factor>.ast ) ELSE
IF tkDiv THEN node_Div( <Termino>.ast, <Factor>.ast ) ELSE
IF tkY THEN node_Y( <Termino>.ast, <Factor>.ast ) ELSE
IF tkPorCien THEN node_PorCien( <Termino>.ast, <Factor>.ast ) ELSE
IF tkCmp THEN node_Cmp( <Termino>.ast, <Factor>.ast ) ENDIF} )*

El método que parsea a <Termino> empieza llamando al método que parsea a <Factor>. Si el método que parsea a <Factor> retorna con éxito, <Factor> vuelve con dos atributos sintetizados: el atributo isident, que indica si <Factor> es un identificador o no lo es y el atributo ast, que contiene el AST de ese <Factor>. Esos dos valores promocionan hacia arriba copiandose en el <Termino>. Si en este punto se lee de la entrada un token tkMul, tkDiv, tkY, tkPorCien o tkCmp, entonces se afirma que <Termino> no es un identificador, y se construye un node_Mul, node_Div, node_Y, node_PorCien o un node_Cmp con lo que hay en <Termino>.ast hasta ese momento y con el <Factor>.ast recién calculado. Las sucesivas iteraciones de la clausura irán construyendo correctamente las ramas del AST.

<Factor> tkIdent {<Factor>.isident = 1} {Factor.ast = node_Ident( tkIdent.lex )}
<Factor> tkNrEnter {<Factor>.isident = 0} {Factor.ast = node_NrEnter( tkNrEnter.val )}
<Factor> tkNrReal {<Factor>.isident = 0} {Factor.ast = node_NrReal( tkNrReal.val )}
<Factor> tkOpTorio {optor = tkOpTorio.lex}
tkAbrPar tkIdent {id = tkIdent.lex}
tkComa <Rango> tkComa <Expresion>
{<Factor>.ast=node_OpTorio(optor, id, Rango.ast1, Rango.ast2, <Expresion>.ast )
tkCiePar {<Factor>.isident = 0}
<Factor> tkAbrPar <Expresion> {<Factor>.ast = <Expresion>.ast}
<Factor> tkCiePar {<Factor>.isident = 0}
<Factor> (tkMenos | tkMas | tkNo )<Factor>
{IF tkMenos THEN <Factor>.ast = node_MenosUnario(<Factor>.ast) ELSE
{IF tkMas THEN <Factor>.ast = node_MasUnario(<Factor>.ast) ELSE
{IF tkNo THEN <Factor>.ast = node_No(<Factor>.ast) ENDIF}

El método que parsea a <Factor> es el punto de la gramática atribuida donde aparecen las condiciones de parada de la recursion. Si desde se deriva un tkIdent, entonces isident es cierto. En cualquier otro caso isident es falso. El resto de las acciones semanticas son para construir los distintos nodos hoja del árbol: node_Ident, node_NrEnter, node_NrReal, node_OpTorio, node_MenosUnario, node_MasUnario o node_No.

<Rango> <Expresion> {<Rango>.ast1 = <Expresion>.ast}
<Factor> tkPtoPto
<Factor> <Expresion> {<Rango>.ast2 = <Expresion>.ast}

Si el <Rango> se parsea correctamente, retorna con dos atributos sintetizados ast1 y ast2 correspondientes a los AST de las expresiones 1 y 2 de los operatorios.

Relación de comprobaciones semánticas

El análisis semántico debe comprobar que la semántica de lo que se va leyendo por la entrada es valida. Entre otras cosas determina si el tipo de los resultados intermedios es correcto, comprueba que los argumentos de un operador pertenecen al conjunto de operadores posibles, comprueba si los operadores son compatibles entre si, etc. Si suponemos que i es una variable declarada de tipo entero y x de tipo real, para el siguiente fragmento de código en m2k2,

    i <- x * x

el analizador léxico devuelve la secuencia de tokens:

    tkIdent('i')
    tkAsign
    tkIdent('x')
    tkMul
    tkIdent('x')

Los errores que puede detectar el analizador sintáctico son aquellos que violan la gramática GDPR, aunque en este caso el analizador sintáctico dice que los tokens llegan en el orden correcto. Sin embargo, el analizador semántico debe mostrar un error semántico que diga que el tipo del identificador que actúa como receptor de la asignación es menos general que el tipo de la expresión que se pretende asignar.

El método parse_Linea devuelve sobre AST el árbol de sintaxis abstracta de la línea analizada.

    AST = SynAna.parse_Linea()

El analizador semántico realiza las comprobaciones semánticas sobre ese AST. Para ello, cada nodo del AST cuenta con un método check que comprueba la semántica de ese nodo y a su vez dispara las comprobaciones semánticas oportunas sobre sus nodos hijos. Todo el proceso de comprobaciones semánticas comienza en el programa principal con la llamada:

    AST.check()

Se enumeran a continuación las comprobaciones semánticas realizadas en cada uno de los nodos del árbol de sintaxis abstracta.

    node_Declaracion.check( )

Comprueba si en la lista de identificadores que se pretende declarar hay algún identificador que ya ha sido previamente declarado. También comprueba si en esa lista hay dos identificadores que tengan el mismo lexema.

    node_Asignacion.check( )

Comprueba si el identificador que actúa como receptor de la asignación ha sido previamente declarado. También comprueba que el tipo del receptor de la asignación no sea menos general que el tipo de la expresión que se asigna. El conflicto con el no-identificador como receptor de una asignación lo resuelve la gramática atribuida tal y como se explica en un apartado anterior que habla sobre el esquema de traducción.

    node_Expresion.check( )

Comprueba la semántica de la expresión que cuelga de este nodo y se devuelve su tipo.

    node_MasBinario.check( )

Comprueba la semántica de los dos términos que hacen de operandos de este nodo y devuelve el tipo que genera la suma binaria después de realizar el typecasting oportuno.

    node_MenosBinario.check( )

Comprueba la semántica de los dos términos que hacen de operandos de este nodo y devuelve el tipo que genera la resta binaria después de realizar el typecasting oportuno.

    node_O.check( )

Comprueba que los dos términos que hacen de operandos de este nodo sean de tipo entero y devuelve el tipo "enter".

    node_Mul.check( )

Comprueba la semántica de los dos términos que hacen de operandos de este nodo y devuelve el tipo que genera la multiplicación después de realizar el typecasting oportuno.

    node_Div.check( )

Comprueba la semántica de los dos términos que hacen de operandos de este nodo y devuelve el tipo que genera la división después de realizar el typecasting correspondiente.

    node_Y.check( )

Comprueba que los dos factores que hacen de operandos de este nodo sean de tipo entero y devuelve el tipo "enter".

    node_PorCien.check( )

Comprueba que los dos factores que hacen de operandos de este nodo sean de tipo entero y devuelve el tipo "enter"

    node_Cmp.check( )

Comprueba la semántica de los dos factores que hacen de operandos de este nodo y devuelve el tipo enter.

    node_Ident.check( )

Actúa como condición de parada de la recursividad. Comprueba si el identificador de este nodo ha sido previamente declarado en la tabla de símbolos y en caso afirmativo devuelve el tipo de ese identificador.

    node_NrEnter.check( )

Actúa como condición de parada de la recursividad. Devuelve el tipo "enter".

    node_NrReal.check( )

Actúa como condición de parada de la recursividad. Devuelve el tipo real.

    node_MasUnario.check( )

Comprueba la semántica del factor que acompaña a este nodo y devuelve su tipo.

    node_Menos.check( )

Comprueba la semántica del factor que acompaña a este nodo y devuelve su tipo.

    node_No.check( )

Comprueba que el factor que hace de operando de este nodo sea de tipo entero y devuelve el tipo "enter".

    node_OpTorio.check( )

Comprueba si el identificador que actúa como variable muda esta declarado y es de tipo "enter". Si esto se cumple, comprueba si las expresiones 1 y 2 del operatorio (rango mínimo y rango máximo) son de tipo "enter". Si esto también se cumple, comprueba con el método issilent() si la variable muda de este operatorio aparece en la lista "silent". Cuando se empieza a evaluar la semántica de la expresión 3 del operatorio, la lista silent contiene los identificadores de las variables que se estan utilizando como mudas en operatorios de nivel superior. Si la variable no se esta usando como variable muda en ningun operatorio de nivel superior, se inserta en la lista silent usando el método setsilent antes de evaluar la semántica de la expresión 3 de ese operatorio. Cuando se terminan las comprobaciones semánticas sobre la expresión 3, se usa el método setnotsilent() para eliminar de la lista silent la variable muda anteriormente insertada en ella. Si todo va bien, al terminar las comprobaciones semánticas del operatorio de nivel mas alto, la lista "silent" debe estar vacia. En caso de que se produzca algun error semántico durante las comprobaciones semánticas del operatorio, la lista "silent" se vacia explícitamente con el método clearallsilent() para que las variables que fueron insertadas en esa lista no sigan ahí al comenzo del análisis de la nueva línea. Todo está perfectamente sincronizado para cumplir la restricción semántica que dice que en el interior de la expresión 3 de un operatorio no puede aparecer otro operatorio que utilice como variable muda una variable que se este usando como muda de otro operatorio de nivel superior en esa misma línea.

Errores semánticos

Para gestionar los errores semánticos se implementa la clase SemError en el módulo semantico.py.

    class SemError:
        def __init__( message )
        def __str__( )

El constructor de esta clase recibe como parámetro una cadena de texto que contiene el mensaje informativo del error. El método __str__ construye el mensaje de error que se imprime por el stderr al imprimir un objeto de esta clase.

Cuando se incumple alguna de las comprobaciones semánticas expuestas en la sección anterior se produce un error semántico. El analizador semántico lanza una excepción SemError junto con el mensaje de error que construye la clase SemError

    raise SemError, SemError( message )

El programa principal captura la excepción y la muestra por el stderr. Sirvan las siguientes situaciones como ejemplos:

    >>> enter i
    >>> real x
    >>> i <- x
    Semantic Error: incorrect typecast in assignment, real i expected
    >>> 1&1.0
    Semantic Error: expected enter operands in binary '&' operator
    >>> (+)(i, 1..2, (*)(i, 1..2, i))
    Semantic Error: silent identifier 'i' inside '(*)' operatory is already in use

Los errores semánticos contienen menos información que los errores léxicos o sintácticos. Existe una razón de limpieza de código para defender esta forma de tratar los errores semanticos. Recuerde que tanto el analizador léxico como el sintáctico hacen uso de información contenida en el propio analizador léxico y en los tokens que el analizador léxico proporciona al analizador sintáctico para construir los mensajes de error que se muestran por el stderr. Parece razonable que los analizadores léxico y sintáctico tengan acceso a estas informaciones.

El analizador semántico tambien podría trabajar con tokens y recibir del analizador sintáctico un parámetro con el analizador léxico para seguir dando a los errores el mismo tratamiento que se les daba en los analizadores léxico y sintáctico. Sin embargo, para mantener el código lo mas limpio posible, parece razonable que el analizador semántico deje de trabajar con tokens y se concentre en su cometido, tratando únicamente el tipo y lexema de los identificadores, y en el tipo y valor de las constantes. Por no tener acceso a los tokens se pierde la información sobre la categoría léxica de los tokens o el puntero al primer carácter de la línea donde empieza el token. Por no tener acceso al analizador léxico, se pierde la información sobre la cadena de caracteres de la línea actualmente analizada, el número de la línea actualmente analizada y el nombre del fichero de entrada.

Una vez se atiende el error semántico y se muestra por el stderr el mensaje informativo del error, se omite todo el procesamiento posterior sobre la línea actual y se continua atendiendo la siguiente línea del stdin.

Implementación del analizador semántico

Las acciones semánticas que aparecen en la gramática atribuida construída durante la sección del "Esquema de traducción" se traducen al lenguaje de programación Python y se intercalan entre las acciones del analizador sintáctico. Se ejecutan cuando el analizador sintáctico pasa por ellas. Para ello se modifica en el módulo sintactico.py la implementación del analizador sintáctico correspondiente a la gramática original de la siguiente manera:

  • Los atributos heredados del no terminal <E> se interpretan como parámetros de entrada del método parse_E.
  • Los atributos sintetizados del no terminal <E> se interpretan como parámetros de salida del método parse_E.

Se comienza por definir la clase "Attributes" en el módulo semantico.py como la clase vacía:

    class Attributes:
        pass

El analizador sintáctico, antes de llamar a un método de parsing de un no terminal, crea un objeto de la clase vacía con el nombre del no terminal al que va a llamar, y añade en ese objeto los atributos heredados del no terminal. Este objeto es el único parámetro que se pasa al método de parsing en su llamada. El correspondiente método de parsing crea sobre su parámetro de entrada los atributos sintetizados. Así, la traducción de la regla:

<E> <T> {<R>.h = <T>.s} <R> {<E>.s = <R>.s}

es la siguiente:

    def parse_E(E):
        T = Attributes()
        R = Attributes()
        parse_T(T)
        R.h = T.s
        parse_R(R)
        E.s = R.s

El siguiente es un extracto del código fuente correspondiente al módulo sintáctico.py. En concreto, el código corresponde al método de parsing del no terminal <Sentencia> con su control de errores correspondiente. El lector debe observar el atributo heredado leftisident del no terminal <Asignacion> y el atributo sintetizado isident del no terminal <Expresion>:

    def parse_Sentencia(self, Sentencia):

        Expresion  = Attributes()
        Asignacion = Attributes()

        # <Sentencia> -> <Expresion> <Asignacion>
        if self.a.cat in ["tkIdent", "tkNrEnter", "tkNrReal", "tkOpTorio", "tkAbrPar", "tkMenos", "tkMas", "tkNo"]:
            if self.a.cat == "tkIdent":
                leftIdent = self.a.lex
            if self.parse_Expresion(Expresion) == 0:
                raise SynError, SynError(self.LA, self.a, "expected <Expresion>")
            Asignacion.leftisident = Expresion.isident
            if self.parse_Asignacion(Asignacion) == 0:
                raise SynError, SynError(self.LA, self.a, "expected <Asignacion>")

        # otro
        else:
            raise SynError, SynError(self.LA, self.a, "expected <Expresion>")

        # se construye un nodo de asignacion o un nodo de expresion
        if Asignacion.isnotempty:
            Sentencia.ast = node_Asignacion( leftIdent, Asignacion.ast )
        else:
            Sentencia.ast = node_Expresion( Expresion.ast )

        return 1

El uso de una gramática GPDR para resolver el analizador sintáctico implica la existencia de clausuras y por tanto la existencia de un número potencialmente infinito de atributos en la gramática. Sin embargo, al interpretarlos dentro de un esquema de traducción, este problema desaparece. Los trataremos como parámetros en las correspondientes llamadas. De esta manera, la traducción de,

<E> <T> {<E>.s = <T>.s} (+<T> {<E>.s = <E>.s+<T>.s})*

es simplemente esta:

    def parse_E(E):
        T = Attributes()
        parse_T(T)
        E.s = T.s
        while token.cat == "suma":
            token = LA.getNextToken()
            parse_T(T)
            E.s = E.s + T.s

El siguiente es otro fragmento el código fuente extraído del módulo sintáctico.py. En concreto, el extracto es una versión reducida del código correspondiente al método de parsing del no terminal con el control de errores necesario. Preste especial atención al trato dado a la clausura:

    def parse_Termino(self, Termino):

        Factor = Attributes()

        # <Termino> -> <Factor> ( (tkMul|tkDiv|tkY|tkPorCien|tkCmp) <Factor> )*
        if self.a.cat in ["tkIdent", "tkNrEnter", "tkNrReal", "tkOpTorio", "tkAbrPar", "tkMenos", "tkMas", "tkNo"]:
            if self.parse_Factor(Factor) == 0:
                raise SynError, SynError(self.LA, self.a, "expected <Factor>")
            Termino.isident = Factor.isident
            Termino.ast = Factor.ast
            while self.a.cat in ["tkMul","tkDiv","tkY","tkPorCien","tkCmp"]:
                Termino.isident = 0
                tk = self.a
                self.a = self.LA.getNextToken()
                if self.parse_Factor(Factor) == 0:
                    raise SynError, SynError(self.LA, self.a, "expected <Factor>")
                if tk.cat == "tkMul":
                    Termino.ast = node_Mul( Termino.ast, Factor.ast )
                if tk.cat == "tkDiv":
                    Termino.ast = node_Div( Termino.ast, Factor.ast )
                if tk.cat == "tkY":
                    Termino.ast = node_Y( Termino.ast, Factor.ast )
                if tk.cat == "tkPorCien":
                    Termino.ast = node_PorCien( Termino.ast, Factor.ast )
                if tk.cat == "tkCmp":
                    Termino.ast = node_Cmp( Termino.ast, tk.lex, Factor.ast )

            # si a no pertenece a siguientes de p*
            if self.a.cat not in ["tkMas", "tkMenos", "tkO", "tkAsign", "tkEOL", "tkCiePar", "tkPtoPto", "tkComa"]:
                raise SynError, SynError(self.LA, self.a, "expected tkMas, tkMenos, tkO, tkAsign, tkEOL, tkCiePar, tkPtoPto or tkComa")

            # otro
            else:
                raise SynError, SynError(self.LA, self.a, "expected <Termino>")

        return 1

Respecto de la implementación del AST, todos los nodos del AST estan implementados como clases dentro del módulo semantico.py. A continuación se muestra el listado de los prototipos de estas clases:

    class node_Declaracion
        def __init__
        def check( )
        def interpret( )
    class node_Expresion
        def __init__
        def check( )
        def interpret( )
    class node_Asignacion
        def __init__
        def check( )
        def interpret( )
    class node_MasBinario
        def __init__
        def check( )
        def interpret( )
    class node_MenosBinario
        def __init__
        def check( )
        def interpret( )
    class node_O
        def __init__
        def check( )
        def interpret( )
    class node_Mul
        def __init__
        def check( )
        def interpret( )
    class node_Div
        def __init__
        def check( )
        def interpret( )
    class node_Y
        def __init__
        def check( )
        def interpret( )
    class node_PorCien
        def __init__
        def check( )
        def interpret( )
    class node_Cmp
        def __init__
        def check( )
        def interpret( )
    class node_Ident
        def __init__
        def check( )
        def interpret( )
    class node_NrEnter
        def __init__
        def check( )
        def interpret( )
    class node_NrReal
        def __init__
        def check( )
        def interpret( )
    class node_MasUnario
        def __init__
        def check( )
        def interpret( )
    class node_MenosUnario
        def __init__
        def check( )
        def interpret( )
    class node_No
        def __init__
        def check( )
        def interpret( )
    class node_OpTorio
        def __init__
        def check( )
        def interpret( )

Continuar leyendo el generación de resultados y código fuente

Seguidores