1.3.2 Lenguajes artificiales
El lenguaje artificial, en oposición al natural, tiene como finalidad evitar –justamente- los inconvenientes de ambigüedad y vaguedad de los lenguajes naturales u ordinarios y, por ello, presenta un grado de artificialidad y convencionalidad mucho mayor por lo que se refiere a la construcción de símbolos y al significado que se les asigna. Símbolos y significados no pertenecen a ninguna comunidad natural de hablantes, sino a grupos de hablantes relacionados por objetivos científicos o técnicos. El lenguaje artificialmente construido se divide en técnico y formal.
Los lenguajes artificiales son aquellos que los humanos hemos creado para comunicarnos
Las computadoras sólo saben 0 y 1
Un lenguaje de programación es un lenguaje artificial que puede ser usado para controlar el comportamiento de una máquina, especialmente una computadora. Estos se componen de un conjunto de reglas sintácticas y semánticas que permiten expresar instrucciones que luego serán interpretadas. Debe distinguirse de "lenguaje informático", que es una definición más amplia, puesto estos incluyen otros lenguajes como son el HTML o PDF que dan formato a un texto y no es programación en sí misma. El programador es el encargado de utilizar un lenguaje de programación para crear un conjunto de instrucciones que, al final, constituirá un programa o subprograma informático. En su uso, un lenguaje de programación puede acercarse a la forma humana de expresarse y, por eso, este tipo de lenguajes es llamado de alto nivel. Esto significa que utilizan palabras y formas en sus estructuras que se asemejan al lenguaje natural (especialmente al inglés). En cambio, aquellos lenguajes que se aproximan más a la forma en la cual la computadora se maneja, son llamados lenguajes de bajo nivel. Esto significa que lo que el programador deba escribir se acercará al lenguaje máquina, que es, en definitiva, lo que las computadoras pueden interpretar. De todas maneras, un lenguaje de programación difiere en muchos aspectos de un lenguaje humano.
Un código escrito en un lenguaje de programación específico siempre se interpreta de la misma manera (no como los idiomas humanos ambiguos), los errores son mucho más significativos (a tal punto de que un código puede volverse ininterpretable por la computadora), etc. El código fuente es el conjunto de instrucciones que conforman un programa (o subprograma o módulo). El código fuente debe ser compilado para poder ser interpretado y ejecutado por la computadora. La compilación traduce el código fuente (que depende del lenguaje de programación) a un lenguaje máquina (que depende del sistema de la máquina). Existen lenguajes del tipo script que son directamente ejecutados por un intérprete y no necesitan compilación. Los lenguajes de programación pueden clasificarse según el paradigma que usan en: procedimentales, orientados a objetos, funcionales, lógicos, híbridos, etc., clasificación que depende de motivos que escapan al alcance de este artículo. Ejemplos Lenguajes de programación: php, prolog, asp, actionscript, ada, python, pascal, c, basic, java, etc.
Un lenguaje artificial permite implementar un algoritmo en una computadora para resolver un problema.
Clasificación de Chomsky.
Lenguajes sin restricciones (gramática 0)
Lenguajes dependientes del contexto (tipo 1)
Lenguajes independientes del contexto (tipo2)
Lenguajes regulares (tipo 0 no presentan ambigüedades, lo ideal para representarlas en una computadora)
1.3.3 Proceso de la comunicación
COMUNICACIÓN: es un proceso mediante el cual un sistema transmite información a otro sistema que es capaz de recibirla. Según este sistema existe:
? Emisor, persona que emite el mensaje o información.
? Receptor, persona o personas que reciben el mensaje.
? Mensaje, información que transmite lo que se quiere decir.
? Canal, medio o via utilizado para transmitir el mensaje.
? Código, conjunto de signos y reglas necesarios para la elaboración del mensaje tanto el emisor como el receptor han de conocer estas reglas de codificación y descodificación.
El término LENGUAJE en sentido estricto sólo se tendría que aplicar para referirse al humano debido a sus características especiales, en este sentido los llamados lenguajes de los animales no tendría que considerarse lenguaje si no SISTEMA DE COMUNICACIÓN.
El hombre puede enseñar muchas cosas a los animales pero lo que nunca ha conseguido es enseñarle a hablar, ni los animales más inteligentes pueden llegar a hablar como los humanos (pueden por medio de la repetición aprender un poco de vocabulario).
Traductor: En un sentido orientado hacia la computación, un traductor, de manera general, es un software que toma como entrada un programa escrito en un código llamado fuente y genera como salida otro programa en un código llamado objeto.
Algunos ejemplos de traductores son los compiladores (toma como entrada código en alto nivel y genera como salida código en bajo nivel), los interpretes (toma como entrada código en alto nivel y genera como salida un código intermedio), los preprocesadores (toma como entrada código en alto nivel y genera como salida código en alto nivel) y el ensamblador (toma como entrada código en ensamblador y genera como salida código en bajo nivel).
Su estructura podría ser expresada de la siguiente manera:
Existen distintos tipos de traductores, entre ellos destacan:
EnsambladoresEs un tipo de traductor que convierte programas escritos en lenguaje ensamblador en programas escritos en código máquina.
Ensambladores.
El término ensamblador (del inglés assembler) se refiere a un tipo de programa informático que se encarga de traducir un fichero fuente escrito en un lenguaje ensamblador, a un fichero objeto que contiene código máquina, ejecutable directamente por la máquina para la que se ha generado. El propósito para el que se crearon este tipo de aplicaciones es la de facilitar la escritura de programas, ya que escribir directamente en código binario, que es el único código entendible por la computadora, es en la práctica imposible. La evolución de los lenguajes de programación a partir del lenguaje ensamblador originó también la evolución de este programa ensamblador hacia lo que se conoce como programa compilador.
Funcionamiento
El programa lee el fichero escrito en lenguaje ensamblador y sustituye cada uno de los códigos mnemotécnicos que aparecen por su código de operación correspondiente en sistema binario.
Tipos de ensambladores
Podemos distinguir entre tres tipos de ensambladores:
Ensambladores básicos. Son de muy bajo nivel, y su tarea consiste básicamente en ofrecer nombres simbólicos a las distintas instrucciones, parámetros y cosas tales como los modos de direccionamiento. Además, reconoce una serie de directivas (o meta instrucciones) que indican ciertos parámetros de funcionamiento del ensamblador.
Ensambladores modulares, o macro ensambladores. Descendientes de los ensambladores básicos, fueron muy populares en las décadas de los 50 y los 60, antes de la generalización de los lenguajes de alto nivel. Hacen todo lo que puede hacer un ensamblador, y además proporcionan una serie de directivas para definir e invocar macroinstrucciones (o simplemente, macros).
Ensambladores modulares 32-bits o de alto nivel. Son ensambladores que aparecieron como respuesta a una nueva arquitectura de procesadores de 32 bits, muchos de ellos teniendo compatibilidad hacia atrás pudiendo trabajar con programas con estructuras de 16 bits. Además de realizar la misma tarea que los anteriores, permitiendo también el uso de macros, permiten utilizar estructuras de programación más complejas propias de los lenguajes de alto nivel.
Preprocesadores
Traduce un lenguaje de alto nivel a otro, cuando el primero no puede pasar a lenguaje máquina directamente. .
Los preprocesadores producen la entrada para un compilador, y pueden realizar las funciones siguientes:
Procesamiento de macros. Un preprocesador puede permitir a un usuario definir macros, que son abreviaturas de construcciones más grandes.
Inclusión de archivos. Un preprocesador puede insertar archivos de encabezamiento en el texto del programa. Por ejemplo, el preprocesador de C hace que el contenido del archivo reemplace a la proposición #include cuando procesa un archivo que contenga a esa proposición.
Preprocesadores "racionales". Estos preprocesadores enriquecen los lenguajes antiguos con recursos más modernos de flujo de control y de estructuras de datos. Por ejemplo, un preprocesador de este tipo podría proporcionar al usuario macros incorporadas para construcciones, como proposiciones while o if, en un lenguaje de programación que no las tenga.
Extensiones a lenguajes. Estos preprocesadores tratan de crear posibilidades al lenguaje que equivalen a macros incorporadas. Por ejemplo, el lenguaje Equel es un lenguaje de consulta de base de datos integrado en C. El preprocesador considera las proposiciones que empiezan con ## como proposiciones de acceso a la base de datos, sin relación con C, y se traducen a llamadas de procedimiento a rutinas que realizan el acceso a la base de datos.
Los procesadores de macros tratan dos clases de proposiciones: definición de macros y uso de macros. Las definiciones normalmente se indican con algún carácter exclusivo o palabra clave, como define o macro. Constan de un nombre para la macro que se está definiendo y de un cuerpo, que constituye su definición. A menudo, los procesadores de macros admiten parámetros formales en su definición, esto es, símbolos que se reemplazarán por valores (en este contexto, un "valor" es una cadena de caracteres). El uso de una macro consiste en dar nombre a la macro y proporcionar parámetros reales, es decir, valores para sus parámetros formales. El procesador de macros sustituye los parámetros reales por lo parámetros formales del cuerpo de la macro; después, el cuerpo transformado reemplaza el uso de la propia macro.
Preprocesadores:
Macros (expansión de funciones)
Inclusión de archivos (bibliotecas)
Procesadores racionales
Extensiones al leguaje (inclusión de ensamblador en C)
Intérpretes
Se trata de traductores-ejecutores ya que con cada instrucción realizan un proceso triple de lectura-traducción-ejecución. Son relativamente lentos, pero muy buenos para la depuración de programas. La interpretación es una de las formas de ejecución de los programas de computadoras, la otra es la compilación.El término "intérprete" puede hacer referencia al programa que ejecuta el código fuente que acaba de ser traducido a una forma intermedia, o puede hacer referencia al programa que lleva a cabo tanto la traducción como la ejecución.
Lenguajes interpretados: PHP, PERL, BASIC
Intérpretes vs compiladoresCualquier lenguaje puede ser ejecutado tanto vía intérprete o vía compilador, pero algunos lenguajes suelen asociarse más a una vía que a la otra, y por esto son llamados "lenguajes interpretados" o "lenguajes compilados" respectivamente.También puede darse que un programa contenga partes que son implementadas via intérprete y otras vía compilador.También existen intérpretes que incluyen cierta "compilación" en el medio. Son aquellos que compilan a un código intermedio llamado bytecode, que es más eficiente de ejecutar que hacerlo directamente desde el código fuente.En general, la principal desventaja de los intérpretes, es que cuando un programa es interpretado, suele ejecutarse más lento que si el mismo programa estuviese compilado. Esto se debe a que el intérprete debe analizar cada sentencia en el programa en cada ejecución (un análisis en tiempo real). También el acceso a variables es más lento en un intérprete, porque mapear los identificadores para almacenar las localizaciones debe hacerse repetidas veces en tiempo real.
Compiladores
Es el tipo de traductor más conocido. Se trata de un programa que traduce código fuente escrito en un lenguaje de alto nivel (Pascal) en código máquina (no siempre). Son más rápidos que los intérpretes pero presentan mayor dificultad a la hora de detectar errores.
Partes de un compilador
La construcción de un compilador involucra la división del proceso en una serie de fases que variará con su complejidad. Generalmente estas fases se agrupan en dos tareas: el análisis del programa fuente y la síntesis del programa objeto.
Análisis: Se trata de la comprobación de la corrección del programa fuente, e incluye las fases correspondientes al Análisis Léxico (que consiste en la descomposición del programa fuente en componentes léxicos), Análisis Sintáctico (agrupación de los componentes léxicos en frases gramaticales ) y Análisis Semántico (comprobación de la validez semántica de las sentencias aceptadas en la fase de Análisis Sintáctico).
Síntesis: Su objetivo es la generación de la salida expresada en el lenguaje objeto y suele estar formado por una o varias combinaciones de fases de Generación de Código (normalmente se trata de código intermedio o de código objeto) y de Optimización de Código (en las que se busca obtener un código lo más eficiente posible).
Alternativamente, las fases descritas para las tareas de análisis y síntesis se pueden agrupar en Front-end y Back-end:
Front–end: es la parte que analiza el código fuente, comprueba su validez, genera el árbol de derivación y rellena los valores de la tabla de símbolos. Esta parte suele ser independiente de la plataforma o sistema para el cual se vaya a compilar, y está compuesta por las fases comprendidas entre el Análisis Léxico y la Generación de Código Intermedio.
Back–end: es la parte que genera el código máquina, específico de una plataforma, a partir de los resultados de la fase de análisis, realizada por el Front End.
Esta división permite que el mismo Back End se utilice para generar el código máquina de varios lenguajes de programación distintos y que el mismo Front End que sirve para analizar el código fuente de un lenguaje de programación concreto sirva para generar código máquina en varias plataformas distintas. Suele incluir la generación y optimización del código dependiente de la máquina.
El código que genera el Back End normalmente no se puede ejecutar directamente, sino que necesita ser enlazado por un programa enlazador (linker).
Ejemplos de compiladores: C, C++, Pascal, etc.
El autor, Alfred ilustra las fases de un compilador como lo muestra la siguiente figura
Análisis Léxico.- Separa las Cadenas en unidades llamadas Tokens y las compara con la tabla de símbolos, El la tabla de símbolos están definidos todos los símbolos (caracteres), que el lenguaje reconocerá. Los únicos errores que podría generar es que encuentre algún carácter desconocido (inexistente en la tabla de símbolos).
Análisis Sintáctico.- Compara los resultados obtenidos en la anterior etapa con las gramáticas. Es decir que haya cadenas validas en las Variables, palabras reservadas, etc.
Análisis Semántico.- Checa que exista coherencia entre los identificadores y que la expresión sea correcta.
Generación de Código intermedio.- Traduce de código fuente a un código como ensamblador acercándose a el lenguaje maquina.
Optimización.- Reduce en forma, Tamaño y tiempo el código anterior.
Generadores de Código para Compiladores (Compilador de Compilador)
Se han creado algunas herramientas generales para el diseño automático de componentes específicos de compilador. Estas herramientas utilizan lenguajes especializados para especificar e implantar el componente, y pueden utilizar algoritmos bastantes complejos. Las herramientas más efectivas son las que ocultan los detalles del algoritmo de generación y producen componentes que se pueden integrar con facilidad al resto del compilador. La siguiente es una lista de algunas herramientas útiles para la construcción de compiladores:
1.- Generadores de analizadores sintácticos. Estos generadores producen analizadores sintácticos, normalmente a partir de una entrada fundamentada en una gramática independiente del contexto. En los primeros compiladores, el análisis sintáctico consumía no sólo gran parte del tiempo, de ejecución del compilador, sino gran parte del esfuerzo intelectual de escribirlo. Esta fase se considera ahora una de las más fáciles de aplicar. Muchos de los generadores de analizadores sintácticos utilizan poderosos algoritmos de análisis sintáctico, y son demasiado complejos para realizarlos manualmente.
2.- Generadores de analizadores léxicos. Estas herramientas generan automáticamente analizadores léxicos, por lo general a partir de una especificación basada en expresiones regulares. La organización básica del analizador léxico resultante es en realidad un autómata finito.
3.- Dispositivos de traducción dirigida por la sintaxis. Estos producen grupos de rutinas que recorren el árbol de análisis sintáctico. La idea básica es que se asocian una o más "traducciones" con cada nodo del árbol de análisis sintáctico, y cada traducción se define partiendo de traducciones en sus nodos vecinos en el árbol.
4.- Generadores automáticos de código. Tales herramientas toman un conjunto de reglas que definen la traducción de cada operación del lenguaje intermedio al lenguaje de máquina para la máquina objeto. Las reglas deben incluir suficiente detalle para poder manejar los distintos métodos de acceso posibles a los datos; por ejemplo, las variables pueden estar en registros, en una posición fija (estática) de memoria o pueden tener asignada una posición en una pila. La técnica fundamental es la de "concordancia de plantillas". Las proposiciones de código intermedio se reemplazan por "plantillas" que representan secuencias de instrucciones de máquina, de modo que las suposiciones sobre el almacenamiento de las variables concuerden de plantilla a plantilla. Como suele haber muchas opciones en relación con la ubicación de las variables (por ejemplo, en uno o varios registros o en memoria), hay muchas formas posibles de "cubrir" el código intermedio con un conjunto dado de plantillas, y es necesario seleccionar una buena cobertura sin una explosión combinatoria en el tiempo de ejecución del compilador.
5.- Dispositivos para análisis de flujo de datos. Mucha de la información necesaria para hacer una buena optimación de código implica hacer un "análisis de flujo de datos", que consiste en la recolección de información sobre la forma en que se transmiten los valores de una parte de un programa a cada una de las otras partes. Las distintas tareas de esta naturaleza se pueden efectuar esencialmente con la misma rutina, en la que el usuario proporciona los detalles relativos a la relación que hay entre las proposiciones en código intermedio y a la información que se está recolectando.
LEX es una herramienta utilizada en la especificación de analizadores léxicos para varios lenguajes. Esta herramienta se denomina compilador Lex (PCLEX para el entorno DOS), y la especificación de su entrada, lenguaje Lex. El estudio de una herramienta existente permitirá mostrar cómo, utilizando expresiones regulares, se puede combinar la especificación de patrones con acciones, por ejemplo, haciendo entradas en una tabla de símbolos, cuya ejecución se pueda pedir a un analizador léxico. Se pueden utilizar las especificaciones tipo Lex aunque no se disponga de un compilador Lex; las especificaciones se pueden transcribir manualmente a un programa operativo empleando técnicas de diagramas de transiciones.
FLEX es una herramienta para generar programas capaces de reconocer patrones de texto. El reconocimiento de patrones es muy útil en muchas aplicaciones. El usuario establece las reglas a usar y Flex generará el programa que buscará esos patrones. La razón por la que la gente usa Flex es porque es mucho más fácil establecer las reglas a usar que escribir directamente un programa que busque el texto.
PATRONES
Los patrones en la entrada se escriben utilizando un conjunto extendido de expresiones regulares. Estas son:
YACC significa "otro compilador de compiladores más" (del inglés Yet Another Compiler-Compiler), lo que refleja la popularidad de los generadores de analizadores sintácticos al principio de los años setenta, cuando S.C. Johnson creó la primera versión de Yacc. Este generador se encuentra disponible como una orden del sistema Unix y se ha utilizado para facilitar la implantación de cientos de compiladores.
JAVACC. (Java Compiler Compiler – Metacompilador en Java) es el principal metacompilador en JavaCC, tanto por sus posibilidades, como por su ámbito de difusión. Se trata de una herramienta que facilita la construcción de analizadores léxicos y sintácticos por el método de las funciones recursivas, aunque permite una notación relajada muy parecida a la BNF. De esta manera, los analizadores generados utilizan la técnica descendente a la hora de obtener el árbol sintáctico.
CARACTERÍSTICAS GENERALES:
JavaCC integra en una misma herramienta al analizador lexicográfico y al sintáctico, y el código que genera es independiente de cualquier biblioteca externa, lo que le confiere una interesante propiedad de independencia respecto al entorno. A grandes rasgos, sus principales características son las siguientes:
• Genera analizadores descendentes, permitiendo el uso de gramáticas de propósito general y la utilización de atributos tanto sintetizados como heredados durante la construcción del árbol sintáctico. • Las especificaciones léxicas y sintácticas se ubican en un solo archivo. De esta manera la gramática puede ser leída y mantenida más fácilmente.
No obstante, cuando se introducen acciones semánticas, recomendamos el uso de ciertos comentarios para mejorar la legibilidad.
• Admite el uso de estados léxicos y la capacidad de agregar acciones léxicas incluyendo un bloque de código Java tras el identificador de un token.
• Incorpora distintos tipos de tokens: normales (TOKEN), especiales (SPECIAL_TOKEN), espaciadores (SKIP) y de continuación (MORE). Ello permite trabajar con especificaciones más claras, a la vez que permite una mejor gestión de los mensajes de error y advertencia por parte de JavaCC en tiempo de metacompilación.
• Los tokens especiales son ignorados por el analizador generado, pero están disponibles para poder ser procesados por el desarrollador.
• La especificación léxica puede definir tokens de manera tal que no se diferencien las mayúsculas de las minúsculas bien a nivel global, bien en un patrón concreto.
• Adopta una notación BNF propia mediante la utilización de símbolos propios de expresiones regulares, tales como (A)*, (A)+.
• Genera por defecto un analizador sintáctico LL(1). No obstante, puede haber porciones de la gramática que no sean LL(1), lo que es resuelto en JavaCC mediante la posibilidad de resolver las ambigüedades desplazar/desplazar localmente en el punto del conflicto. En otras palabras, permite que el así. se transforme en LL(k) sólo en tales puntos, pero se conserva LL(1) en el resto de las reglas mejorando la eficiencia.
• De entre los generadores de analizadores sintácticos descendentes, JavaCC es uno de los que poseen mejor gestión de errores. Los analizadores generados por JavaCC son capaces de localizar exactamente la ubicación de los errores, proporcionando información diagnóstica completa.
• Permite entradas codificadas en Unicode, de forma que las especificaciones léxicas también pueden incluir cualquier carácter Unicode. Esto facilita la descripción de los elementos del lenguaje, tales como los identificadores Java que permiten ciertos caracteres Unicode que no son ASCII.
• Permite depurar tanto el analizador sintáctico generado como el lexicográfico, mediante las opciones DEBUG_PARSER, DEBUG_LOOKAHEAD, y DEBUG_TOKEN_MANAGER. • JavaCC ofrece muchas opciones diferentes para personalizar su comportamiento y el comportamiento de los analizadores generados.
• Incluye la herramienta JJTree, un preprocesador para el desarrollo de árboles con características muy potentes.
• Incluye la herramienta JJDoc que convierte los archivos de la gramática en archivos de documentación.
• Es altamente eficiente, lo que lo hace apto para entornos profesionales y lo ha convertido en uno de los metacompiladores más extendidos.
Autor:
Julio Cesar Neira Hern?ndez
Yesenia Rocha Cisneros
Yoc Rodrigo Pacheco Hern?ndez
Catedr?tico: 15/02/2010
Página anterior | Volver al principio del trabajo | Página siguiente |