Introducción

Qué se va a ver

Como parte del trabajo de esta asignatura, el alumno diseñará e implementará un compilador de un lenguaje de programación. Esto no es una tarea trivial, por lo que durante el curso se mostrarán las técnicas necesarias para conseguir este objetivo.

Concretamente, lo que se verá en esta asignatura es cómo construir un procesador de lenguajes. Como se verá posteriormente, un procesador es algo más general que un compilador y la razón por que se construirá este último es porque es el caso más representativo y, por ello, la forma más común de ver las características generales de todos los procesadores.

Qué es un Procesador de Lenguajes

Un procesador de lenguajes es una aplicación en la cual una de las entradas es un lenguaje. En capítulos posteriores se definirá formalmente lo que es un lenguaje. Hasta entonces, de manera (muy) informal, se podría describir a un PL como un programa que tiene que tratar con entradas no-triviales. Serían entradas triviales, por ejemplo, aquellas en las que sus componentes tienen una estructura fija, o bien están delimitados por ciertos caracteres y, por tanto, sería fácil separar dichos elementos. Un ejemplo de este tipo de entradas sería un fichero CSV.

DNI ; Nombre ; Cargo
1231234B ; Raúl ; Superjefe
1232345C ; Roberto ; Analista
1233456D ; Elsa ; Diseñadora

En casos como los anteriores no hacen falta técnicas especiales para extraer los elementos del fichero. Puede hacerse fácilmente utilizando las operaciones habituales de manipulación de cadenas de caracteres de cualquier lenguaje de programación.

Sin embargo, hay entradas que son más complicadas de tratar. Por ejemplo, supóngase un traductor de un lenguaje como el siguiente:

void f() {
    int a;

    if (a > 2)
        g(b++);
}

Esta entrada presenta problemas que las anteriores no tienen. Por ejemplo:

  • En el CSV todos los elementos son siempre simples (palabras - strings). Sin embargo, el tratamiento se complica cuando los elementos están compuestos a su vez por otros elementos que tienen su propia estructura particular. En el caso de la función f, esta está formada por sentencias y algunas, como el if, a su vez contiene otras sentencias. Es bastante más difícil delimitar dónde empieza y acaba, por ejemplo, dicho if.
  • En el CSV los elementos vienen en un orden predeterminado (primero el DNI, luego el Nombre...). En cambio, en la función f puede aparecer cualquier secuencia de sentencias en cualquier orden.
  • En el CSV la validez de cada elemento no depende del contexto (es decir, de qué elementos hayan aparecido antes). En cambio, en la función f:
    • Después de la a de int a no puede haber ++ ni ). Sin embargo, después de la a de g(a++), sí puede aparecer el operador de incremento.
    • La línea con la llamada a la función g será válida o no en función de cómo se haya definido antes (y eso si se ha definido).

A los programas que tratan con estas entradas más complicadas es a los que se les denomina Procesadores de Lenguajes (PL a partir de ahora) y son para los que hacen falta técnicas especiales para su construcción — que son las que se verán en esta asignatura.

El caso más habitual de entrada donde se presentan situaciones como las anteriores, aunque no es el único, es el de los lenguajes de programación.

Por qué ver Procesadores

Los tres motivos principales para que un Ingeniero Informática sepa construir un Procesador de Lenguajes son:

  • Saber hacer un parser de una entrada no trivial. Aunque es cierto que no es habitual que se tenga que hacer un lenguaje de programación, sí que surgirán situaciones en las que se necesitará procesar entradas no-triviales (un fichero de configuración a medida, datos extraídos de un programa legacy con un formato específico, etc.). En esos casos, hay que saber, al menos, las técnicas básicas.

  • Aprender más rápido nuevos lenguajes. Para un Informático, el aprender nuevos lenguajes va a ser una tarea constante. Y esto se realizará más rápido si se entienden los términos usados en los PL. Por ejemplo, al aprender un nuevo lenguaje, cuando el manual indique que un if en dicho lenguaje es una expresión en vez de una sentencia, hay que tener claras las implicaciones de esto (si no se saben ahora, se deberían saber al acabar el curso).

  • Entender el lenguaje de programación que se esté usando. A la hora de programar se darán situaciones (típicamente errores) que se entenderán mejor si se sabe qué está haciendo el compilador por dentro y se sabrá, por tanto, cómo proceder. Por ejemplo, cuando el compilador rechace una entrada indicando que la expresión no es un lvalue, se debería entender rápidamente cuál es el problema.

Como efecto adicional, el alumno podrá ver un paradigma de desarrollo de software distinto de los habituales. Los paradigmas tradicionales no tienen un soporte formal completo para el desarrollo a partir de unas especificaciones más o menos formales. Los procesos de análisis, diseño e implementación van alejándose de esta formalidad y adentrándose en una labor con un gran contenido casi artesanal. Sin embargo, el paradigma de desarrollo de compiladores puede formalizar el proceso en mucha mayor medida, de tal manera que las especificaciones mediante metalenguajes (los cuales se verán posteriormente) condicionan gran parte de la implementación, dejando menos sitio a la producción ad hoc.

Utilizar este paradigma de desarrollo puede solucionar problemas de software no sólo del tipo de los compiladores ya que hay muchos procesos que se basan en procesar una corriente de datos de entrada estructurados y pueden ser tratados con este modelo (p.e. conversores de formatos, decodificadores, etc.)

Tipos de Procesadores

Hay varios tipos de PL. Algunos de los más habituales son:

  • Traductor. Es un PL que, además de tener un lenguaje como entrada (como todo PL), tiene también un lenguaje a la salida.
  • Compilador. Es un Traductor en el que el lenguaje de entrada es un lenguaje de programación de alto nivel y el de salida es de bajo nivel. La entrada se denomina código fuente (source code) y lo generado se denomina código objeto (object code).
  • Intérprete. Es un PL que, a diferencia de un Traductor, no produce otro lenguaje como salida (lo cual no quiere decir que no tenga salida). Son ejemplos de intérpretes los entornos en línea de comando (bash, cmd.exe, ...) o lenguajes de programación como javascript, python y ruby.

Lectura Adicional Recomendada

🔎 Para más tipos de Procesadores de Lenguaje, ver el capítulo 2 del cuaderno didáctico Conceptos Básicos de Procesadores de Lenguajes.

Ejemplos de Procesadores

Al hablar de PL, en lo primero que se piensa es en un compilador de un lenguaje de programación. Pero hay que destacar que, aunque sí que es cierto que es el caso más tratado en la literatura, la definición no indica que se tenga que tratar únicamente con lenguajes de programación para ser un PL. Será un PL cualquier programa cuya entrada sea un lenguaje y esta entrada puede ser un fichero de configuración, un texto con estructura particular, etc. De hecho, el PL más instalado y usado no es un compilador.

Algunos otros tipos de PL comunes son:

  • Programas de Ofimática. Además del procesado de sus documentos, suelen permitir al usuario introducir información que hay que evaluar (fórmulas de Excel, campos de Word) o bien expresar consultas de la información (Excel, Access, etc.).
  • Editores. Hoy en día lo habitual es que un editor reformatee el texto y aplique colores. Para ello, tiene que analizar el texto.
  • Navegadores Web. Son los PL más usados (tanto por informáticos como por usuarios no técnicos). En concreto, un navegador es la unión de varios PL: un intérprete de HTML al que se le añadió posteriormente otro de Javascript, otro de CSS, otro de XML, etc.

Lectura Adicional Recomendada

🔎 Para más ejemplos de Procesadores de Lenguaje, ver el capítulo 16 del cuaderno didáctico Conceptos Básicos de Procesadores de Lenguajes.