lunes, 8 de diciembre de 2008

ICML

Introducción

En los años 40 códigos numéricos y un secuenciador de cableado constituían un código de máquina.

Como los códigos numéricos eran muy complejos de manejar se generaron neumónicos y se construyó el lenguaje ensamblador para facilitar la tarea.

El primer lenguaje de alto nivel desarrollado fue FORTRAN, que fue considerado el primer traductor (compilador) pero estaba específicamente dirigido al hardware que se empleaba.

En Europa, en forma paralela apareció un lenguaje que trataba ser independiente del hardware, basado en las gramáticas de Chomsky, así fue creado el ALGOL 58.

Recién en 1958 Strong y su grupo propuso la división del compilador en dos fases (front end y back end) lo que permitió que pueda ser utilizado sobre diferentes hardware.

Siguiendo con la historia, rápidamente podemos comentar que a fines de los 50 Rabin y Scott proponen implementar autómatas deterministas y no deterministas en el análisis lexicográfico de los programas fuentes, Sheridan en el 59, describe un método de parking Luego Floyd introduce técnicas de precedencia de operadores y funciones de precedencia. El parsing descendente recursivo se utilizó en el 61 y en el 68 se definen las gramáticas LL y los parsers predictivos.
En el año 68 Johnson a partir de expresiones regulares construye analizadores léxicos. En definitiva Chomsky y sus trabajos fueron la base de los métodos de análisis léxico y sintáctico. En los 70 surgen los métodos SLR y LALR de parser LR quedando su implementación en los generadores automáticos de parsers. Johnson crea el generador de analizadores sintácticos para funcionar en UNIX.

…………………………..

La generación de código intermedio como tarea de un compilador permite escribir un fuente en cualquier lenguaje a un código independiente del mismo e independiente de la plataforma donde se ejecutará.

Objetivo

El ICML (Intermediate Code Markup Language) y el GSML (Grammar Spec Markup Language) son dos propuestas de simplificación de especificación de código intermedio y gramáticas (respectivamente).

Están inspiradas en el compilador de BNF javacc y jtree, aunque van un poco mas allá de los mismos.

En un principio, estarán basadas en herramientas ya existentes (Lex y Yacc), pero apuntan a ser independientes de los mismos.

Fundamentación

Una de las partes mas complejas del diseño de compiladores es la adición de acciones semánticas a una gramática ya hecha y pulida.

Veamos esta situación: el programador acaba de terminar con la gramática, y está muy contento de verla prolija, sintética y elegante. Un verdadero trabajo de científico, sobre todo la parte de eliminar ese bendito conflicto "shift-reduce" que no aparecía por ningún lado. Atrás quedó el agregar las reglas de recuperación ante el error con su cascada de "reduce-reduce", y otros mensajes extravagantes que emitía el yacc (¿qué significa "pila demasiado chica"? ¿por qué la regla 1034 no se reduce nunca?, ¿me paso a Linux?, etc., etc. ).

Pero ahora viene lo peor ... las acciones semánticas.

De por si solo, este monstruoso trabajo no hace nada mas importante que decidir si un programa fuente es correcto o no, bastante poca cosa después de todo lo que se sufrió.

Manos a la obra, y empecemos a definir una código intermedio suficientemente simple y útil. Mas fácil decirlo que hacerlo.

Al tiempo de luchar con triplas, cuádruplas, un-millón-plas, queda mas o menos plasmado un código no demasiado vergonzoso, y el programador comienza a transpirar poniendo las acciones semánticas que lo generen.

Cada vez que piensa que el fin está mas cerca, descubre que alguna regla provoca atributos sintetizados ¿cambio la regla o pongo una acción semántica intermedia? (ni piensa en cambiar la gramática porque fue demasiado trabajo, pero poner una acción intermedia es poco elegante ...), se le aparece algún caso de triplo que no había previsto y tiene que cambiar todos los ".h" del sistema, y así hasta el infinito.

Todo esto para que, cuando entrega el trabajo, con el orgullo propio de haber hecho algo terriblemente difícil y complicado, el jefe (o en nuestro caso, el profesor) le objete que su código es ilegible, o que sería una buena idea agregar otra forma de pasar parámetros a las funciones. Vuelta a empezar ...

¿Por qué no existe una separación entre gramática y semántica como la que ya hay con la léxica? esto resolvería una gran cantidad de cosas:

  • Una gramática terminada solo hay que modificarla ante nuevos requerimientos.
  • Modificaciones a la gramática impactan muy poco en la semántica.
  • El "parser" genera el árbol de análisis sintáctico independientemente de cual sea el lenguaje objeto.
  • Las semánticas pueden dividirse en varios programas que modifican el árbol un poco por vez. Esto es especialmente útil para realizar optimización sobre el código.

Nuestro objetivo es generar un código intermedio "cuasi-standard" que permita resolver la separación entre las estapas del compilador.

Desarrollo

¿Qué es XML?

XML es la sigla del inglés eXtensible Markup Language (lenguaje de marcado ampliable o extensible) desarrollado por el World Wide Web Consortium (W3C).

Su objetivo principal es conseguir una página web más semántica. Aunque una de las principales funciones con las que nace sería suceder al HTML, separando la estructura del contenido y permitiendo el desarrollo de vocabularios modulares, compatibles con cierta unidad y simplicidad del lenguaje (objetivo que se viene desarrollando a través de la especificación XHTML), tiene otras aplicaciones entre las que destaca su uso como estándar para el intercambio de datos entre diversas aplicaciones o software con lenguajes privados como en el caso del SOAP.

Al igual que el HTML, se basa en documentos de texto plano en los que se utilizan etiquetas para delimitar los elementos de un documento. Sin embargo, XML define estas etiquetas en función del tipo de datos que está describiendo y no de la apariencia final que tendrán en pantalla o en la copia impresa, además de permitir definir nuevas etiquetas y ampliar las existentes.

Son varios los vocabularios desarrollados en XML con el fin de ampliar sus aplicaciones. Podemos considerar fundamentales: XHTML, XSL-FO y XSLT, XLink, XPointer y Schema. Además, existen también versiones para usos específicos, como MathML (fórmulas matemáticas), SVG (gráficos vectoriales), RSS (sindicación de noticias), GML (información geográfica) o XBRL (partes financieros).

wikipedia

¿Qué es ICML?

El icml es una propuesta de código intermedio basado en en XML, en el cual se definen los tags necesarios para traducir la mayoría de los lenguajes de la actualidad. A fines de comenzar con este proyecto, se tomarán los tags necesarios para generar C y Pascal, pero el objetivo a mediano plazo es extenderlo a lenguajes con orientación a objetos (C++, Java y C#), y definir un mecanismo de extensión que permita definir otros "tags" (SQL).


Veamos un ejemplo:



Como se puede observar, el árbol es independiente del lenguaje de entrada, y del uso final que se le quiera dar. Por ejemplo, un optimizador de código que recibiera esta salida podría inmediatamente descubrir que dentro del árbol del "*", solo hay constantes, con lo cual podría reducirlo en tiempo de compilación (independientemente del lenguaje de origen), un graficador podría representar el árbol sin tener que hacer ningún procesamiento demasiado sofisticado, etc.

Como se puede observar, el árbol es independiente del lenguaje de entrada, y del uso final que se le quiera dar. Por ejemplo, un optimizador de código que recibiera esta salida podría inmediatamente descubrir que dentro del árbol del "*", solo hay constantes, con lo cual podría reducirlo en tiempo de compilación (independientemente del lenguaje de origen), un graficador podría representar el árbol sin tener que hacer ningún procesamiento demasiado sofisticado, etc.

domingo, 7 de diciembre de 2008

Lenguaje intermedio en .net

La función de este tipo de compiladores es traducir el código que el programador escribe en cualquiera de los lenguajes soportados por el CLR a un Lenguaje Intermedio estándar (LI). Este lenguaje es un lenguaje independiente de la plataforma.

Todas las herramientas de desarrollo generan el mismo IL, independientemente del lenguaje en el que esté escrito el código fuente, con lo cual las diferencias de implementación desaparecen en el momento en que entra en funcionamiento el CLR. Para que no haya ningún problema en la interacción de lenguajes, los distintos tipos de lenguajes tienen que seguir unas pautas establecidas en él.

Cuando un compilador genera código intermedio, también genera a su vez metadatos. Los metadatos es información que el CLR va a utilizar a en tiempo de ejcución, como puede ser, por ejemplo, la definición de tipos que hay en el código. Estos metadatos, junto con el código intermedio se almacenan en un fichero PE (Portable Executable). Una de las ventajas de este tipo de ejecutable es que al almacenar los metadatos y el código intermedio juntos, el código se describe así mismo, con lo cual ya no es necesario, por ejemplo tener IDLs.

Una herramienta muy interesante para ver como están constituidos estos ficheros PE, es ildasm.exe que se instala junto con el Framework. Esta herramienta nos permite, entre otras cosas, ver como está formado este tipo de ficheros, ver qué código intermedio ha generado cada función escrita y muchas otras cosas interesantes. Podemos encontrar un tutorial dentro de la documentación del Framework.

La especificación de este IL es abierta, con lo cual cualquiera que lo desee puede construir su propio compilador a código intermedio compatible con el CLR.

Compilación JIT

Una vez que se ha realizado un programa y se ha compilado con los compiladores que ofrece la plataforma, el código resultante no se puede ejecutar directamente sobre la máquina, ya que lo que contienen es código intermedio. Para poder ser ejecutados, necesitan previamente pasar por un compilador JIT.

En primer lugar, aclarar lo que significa JIT. Estas siglas pertenecen a Just-In-Time, nombre que recibe este tipo de compilación debido a que se realiza en tiempo de ejecución.

Antes de que el código intermedio anteriormente generado sea ejecutado, debe ser convertido por el JIT en código nativo, esto es, código que pueda entender el computador. Este código es específico para cada arquitectura y lo que le proporciona a .NET independencia de la plataforma, con lo cual, nosotros escribimos el mismo código independientemente de la arquitectura en la que se vaya a ejecutar.

No obstante esto no es siempre necesario, ya que para cierto tipo de aplicaciones, puede resultar ineficiente y hay opciones que permiten que la compilación JIT sólo se realice una única vez cuando la aplicación es instalada en la máquina (compilación en tiempo de instalación) y el código nativo quede almacenado en el sistema, al igual que ocurre con las aplicaciones no pertenecientes a .NET. Esto se hace mediante la herramienta Ngen.exe, en línea de comandos y es el generador de imágenes nativas.

Una de las ventajas de la compilación just-in-time es que no compila todo nada más llamar al ejecutable, esto es, en vez de convertir todo el fichero PE (ver Compiladores de Lenguaje Intermedio) a código nativo, va convirtiendo el código intermedio que vaya necesitando, ahorrando así tiempo y recursos. Además, el código que ya se ha traducido, es almacenado para llamadas posteriores.

En la etapa de generación de código nativo a partir de código intermedio, también tiene lugar un proceso de verificación (siempre va a suceder a menos que el administrador del sistema indique lo contrario). La verificación comprueba el código intermedio y los metadatos para comprobar si el código puede ser considerado como “seguro” (accede únicamente a aquellas direcciones de memoria que puede acceder). No obstante, no todos los lenguajes soportan la generación de código seguro (por ejemplo C++ con los punteros). Si las políticas de sistema requieren la ejecución de código seguro, se lanzará una excepción a la hora de ejecutar dicho código.

Estructura de datos

Las Estructuras de Datos se pueden definir como la organización de la información que permite un determinado lenguaje de programación. Cada estructura posee sus propias características de almacenamiento y recuperación de los datos.
Los Algoritmos constituyen la resolución de problemas computacionales mediante un lenguaje de programación.

Una de las novedades realmente únicas en .NET es la unificación del sistema de tipos que resuelve de forma brillante muchos de los problemas que siempre se han encontrado en el desarrollo de software. El runtime llamado CLR (Common Lenguaje Runtime) que el .NET nos proporciona, presenta de entre sus muchas características el conjunto de tipos común estandarizado para todos los lenguajes soportados, es el llamado CTS (Common Type System).

Common Type System (CTS): Es el conjunto de reglas que han de seguir las definiciones de tipos de datos para que el CLR las acepte. Es decir, aunque cada lenguaje gestionado disponga de su propia sintaxis para definir tipos de datos, en el MSIL resultante de la compilación de sus códigos fuente se han de cumplir las reglas del CTS. Divide además los tipos de datos en dos categorías:

  • Tipos por valor: Son aquellos tipos que contienen los datos, el valor en si. Se almacenan directamente en el disco duro de la computadora y cuando trabajamos con ellos, trabajamos directamente con el valor. No requiere de memoria adicional aparte de la estrictamente necesaria para almacenar el valor. Estos tipos son implementados por el runtime y aumentan considerablemente la velocidad.
  • Tipos por referencia: Son aquellos que hacen referencia a una posición de memoria, están guardados en la pila. Son más lentos que los tipos por valor y ocupan algo más de memoria, a cambio obtienen varias ventajas como por ejemplo que pueden contener métodos, polimorfismo…

Los Algorítmos permiten resolver problemas computacionales mediante lenguajes de programación. Como Ejemplo podemos poner dos de los más usuales:

  • Divide y Vencerás: Consiste en descomponer un problema en subproblemas, resolver cada subproblema y combinar las soluciones. El resultado, es la solución del problema original. Si los subproblemas son todavía demasiado grandes, se utiliza la misma táctica con ellos, esto es, dividirlos a ellos también, utilizando un algoritmo recursivo que vaya dividiendo más el sub-problema hasta que su solución sea trivial
  • Backtracking: El Backtracking o esquema de vuelta atrás, es un esquema que de forma sistemática y organizada, genera y recorre un espacio que contiene todas las posibles secuencias de decisiones. Este espacio se denomina el espacio de búsqueda del problema, y se representa como un árbol sobre el que el algoritmo hace un recorrido en profundidad partiendo de la raíz. Se conoce de antemano el orden en que se van a generar y recorrer sus nodos, y se continúa recorriendo el árbol mientras se cumplan las restricciones. Éste método tiene tres posibles esquemas: encontrar una solución factible, encontrar todas las soluciones factibles, encontrar la mejor solución factible.

¿Cómo compila .net?

El desarrollo de software y la programación es uno de los pilares fundamentales de la informática y al cual se dedican muchas horas de esfuerzo en empresas, colegios, academias y universidades.

Conforme la tecnología va avanzando aparecen nuevas soluciones, nuevas formas de programación, nuevos lenguajes y un sinfín de herramientas que intentan hacer la labor del desarrollador un poco más fácil.

La programación orientada a objetos o los compiladores basados en máquinas virtuales (en muchos casos, multiplataforma), también han supuesto una revolución en la forma de programar.

Microsoft, como empresa desarrolladora de software, es muy consciente de lo importante que es hacer buenos desarrollos y lo complicado que es; por eso, intenta aportar las mejores soluciones al mercado. Actualmente estamos en una época de transición, encaminados hacia un nuevo estilo de programación basada en estándares y para ello Microsoft propone la plataforma .NET.

Un compilador es la pieza base a la hora de desarrollar un programa. Hay compiladores para todos los lenguajes y su función es traducir un programa escrito en un determinado lenguaje a un idioma que el computador entienda.

Si estás interesado en el mundo de los compiladores, Microsoft propone una nueva forma de compilación de forma que se puedan construir programas multilenguaje, de forma que no va a ser necesario conocer muchos lenguajes, sino simplemente el que más te guste. Para ello, se propone una compilación en dos tiempos:




Compilación a código intermedio: Este tipo de compilación es la encargada de traducir desde el código fuente del programador a un lenguaje intermedio independiente de la plataforma.

Compilación JIT a código nativo: En este paso, el código intermedio es traducido a código nativo específico de cada plataforma.

Existen muchas maneras de definir el significado de “programación”, dependiendo del área en la que se engloba poseerá un significado distinto que en otro. Se puede entender la programación como la decisión anticipada de lo que hay que hacer; también se entiende como el conjunto de actividades dirigidas a la consecución de una serie de tareas de forma automática e independiente por un ordenador. Pero en el ámbito puramente informático se puede decir que la programación es la herramienta de la que disponen los desarrolladores de software para crear programas.

Al fin y al cabo, programar no es más que escribir mediante un programa determinado una secuencia de instrucciones pensado para resolver algún tipo de problema. Para ello se apoya en sentencias de control y en la sintaxis del propio lenguaje en el que se esté escribiendo, así pues se verán las sentencias de control de un modo genérico (sin estar ligadas a ningún lenguaje determinado) con ejemplos aclaratorios escritos en C#, para posteriormente ver cómo sería la sintaxis de un lenguaje y con que expresiones contamos para poder ir escribiendo esas secuencias de instrucciones.

Sentencias de control: las Sentencias de Control permiten modelar la estructura del código y el orden por el que irá leyendo el computador las distintas instrucciones.

Programación orientada a objetos: Un lenguaje orientado a objetos se basa en la idea natural de la existencia de un mundo lleno de objetos y que la resolución del problema se realiza en base a ellos.

La Programación ha ido evolucionando al mismo ritmo que lo ha hecho la informática en general. Por este motivo, las técnicas y métodos actuales de programación son muy diferentes de los utilizados hace unos años. La plataforma .NET es la apuesta de Microsoft para el futuro desarrollo de aplicaciones.


Windows .NET Framework: Infórmate de los detalles sobre el funcionamiento de la plataforma .NET

Tecnologías auxiliares: Junto con .NET Framework, podemos encontrar un conjunto de tecnologías auxiliares: ASP.NET, ADO. NET y WinForms.

Capa de Presentación (ASP.NET): Descubre ASP.NET, una tecnología ideal para realizar el desarrrollo de la capa de presentación.

Servicios Web XML: Aquí encontrarás detalladas todas las características de los Servicios Web XML, qué son y cómo funcionan.

Acceso a datos con ADO.NET: ADO.NET es el nueva tecnología para acceso a Bases de Datos relacionada directamente con .NET

Acceso a datos con XML: XML es un standard de representación de datos sobre el cual se basa una parte muy importante de .NET

Compatibilidad ADO.NET - XML: Aprende en esta sección como XML y ADO.NET se complementan para proporcionar un control total sobre nuestros datos.

Windows .NET Framework se puede definir como “un entorno en tiempo de ejecución que facilita la escritura de códgio bueno y robusto de una forma rápida y sencilla, así como su instalación, administración y revisión”. Los componentes y programas que se escriben se ejecutan dentro de este entorno. Está compuesto, principalmente por tres partes:

  • Common Language Runtime (CLR)
  • Librería de clases unificada
  • ASP.NET

El potencial de Windows .NET Framework radica en una capa de abstracción que tiene denominada Common Language Runtime (CLR). Esta capa, entre otras cosas, contiene:

  • Compilador de lenguaje intermedio a código nativo.
  • Garbage Collector
  • Sistema de tipos: Esto permite la compatibilidad e interoperabilidad entre distintos lenguajes. Nos permite, por ejemplo, crear una clase con Visual Basic y que esta misma clase, se herede desde otra en C#. Aquí, entra en juego otra capa distinta, que es la Common Language Specification (CLS). No obstante, hay que tener en cuenta que no todos los lenguajes soportados por .NET son lo suficientemente potentes como para soportar todos los servicios que ofrece CLR.
  • Además, contiene otras funcionalidades como soporte de threads, un potente Debug, un manejador de excepciones, etc.
Otra de las características esenciales de .NET es la libertad que se le da al programador de elegir el lenguaje a utilizar. Si un lenguaje tiene un compilador de código intermedio y puede soportar los tipos de la capa CLS, entonces se puede utilizar dicho lenguaje para una aplicación .NET. Así mismo, existe una librería de clases unificada, independientemente del lenguaje a utilizar, lo que facilita enormemente la coexistencia de programas escritos en más de un lenguaje.

Un poco de historia

HISTORIA DE LOS COMPILADORES
Por lo general códigos numéricos a un secuenciador cableado representando diferentes estados en cada operación, constituían el lenguaje máquina por los años 40, paralelamente a la construcción de los primeros ordenadores digitales.
El hombre se vio frente al problema de minimizar la engorrosa tarea de evitar los códigos numéricos por algún método un tanto mas humano. Se descubrieron claves más fáciles de recordar que los códigos numéricos, traduciéndolas manualmente a lenguaje máquina.
Estas claves constituían el llamado lenguaje ensamblador, quien se generaliza al automatizarse los procesos de traducción.
En la búsqueda de un traductor investigaron hombres como Jhon Backups para IBM, desarrollando un lenguaje de formulas algebraicas. A posteriori se desarrollo un lenguaje (FORTRAN) que permitía escribir formulas matemáticas, fue el primer lenguaje de alto nivel. Se lo consideró el primer traductor (compilador) ya que traducía las formulas matemáticas escritas en lenguaje de alto nivel a lenguaje maquina o lenguaje de bajo nivel. Era un lenguaje específicamente dirigido al hardware que se empleaba.
En Europa en forma paralela surgió una corriente un poco más generalizada, orientada al desarrollo de un lenguaje independiente del hardware, con algoritmos sencillos. Se basaban en las gramáticas libres de contexto publicadas por Chomsky. F. Bauer de la Universidad de Munich, dirigió investigaciones que desarrollaron un lenguaje de usos múltiples sin importar el hardware. Backups trabajó con ellos y en conjunto obtuvieron el ALGOL 58 (1958). Fue un lenguaje modular estructurado en bloques, del cual se realizaron varias mejoras y en él aparecen como:
Formato Libre
Declaración explicita para los identificadores
Estructuras iterativas
Recursividad
Pasaje de parámetros
Definición de sintaxis en notación BNF
Estructuras de bloques
En 1958 Strong y su grupo de investigación propusieron la división de un compilador en dos fases (front end y back end) lo que permitió fuera utilizado en distintos hardware.
El front end se encargaba de realizar los análisis del programa fuente y el back end de generar el código para la maquina objeto. Un lenguaje intermedio llamado UNCOL actuaba de puente entre las dos fases. Y modificando solo el back end se lograba la portabilidad a otra maquina objeto. Esto quedó como base teórica.
A fines de los 50 Rabin y Scott proponen implementar autómatas deterministas y no deterministas en el análisis lexicográfico de los programas fuentes.
Sheridan en el 59, describe un método de parsing de FORTRAN introduciendo paréntesis a los operandos para analizar las expresiones. Luego Floyd introduce técnicas de precedencia de operadores y funciones de precedencia.. El parsing descendente recursivo se utilizó en el 61 y en el 68 se definen las gramáticas LL y los parsers predictivos.
En el año 68 Johnson a partir de expresiones regulares construye analizadores léxicos. En definitiva Chomsky y sus trabajos fueron la base de los métodos de análisis léxico y sintáctico. En los 70 surgen los métodos SLR y LALR de parser LR quedando su implementación en los generadores automáticos de parsers. Johnson crea el generador de analizadores sintácticos para funcionar en UNIX.
ESTRUCTURA DE UN COMPILADOR
Un compilador es un traductor capaz de, a partir de un imput (programa escrito en un lenguaje) generar un output (programa objeto) en lenguaje ensamblador.
Un interprete es un software capaz de tomar una a una sentencias de un programa fuente, la traduce y la ejecuta.
Etapas a nivel lógico del proceso de compilación:
Podrían estar entremezcladas o una dentro de otra. Se agrupan en dos partes el front end o procesos de análisis y back end o procesos de generación y optimización de código objeto. Y se comunican a través de la representación intermedia generada por el front end, quien puede estar constituida por un árbol sintáctico abstracto o bien un programa en un lenguaje intermedio. El front end por lo general suele ser independiente de la maquina objeto para la que va a generar el código. El back end depende siempre de la maquina objeto, no así del lenguaje fuente.
El front end incorpora a sus técnicas conceptos vertidos por el lingüista Noar Chomsky tales como gramáticas