TEMA 1 LENGUAJES DE PROGRAMACIÓN

Anuncio
TEMA 1 LENGUAJES DE PROGRAMACIÓN
Lenguaje
Un lenguaje es un instrumento de comunicación. Para que exista una comunicación debe existir
comprensión mutua
Lenguaje de programación
Un lenguaje de programación tiene como fin la comunicación entre el programador y la máquina,
comunicación, que resulta difícil por ser elementos de diferente naturaleza. Para superar ésta
dificultad se crearon los procesadores de lenguajes, que deben unir el salto semántico entre los
distintos lenguajes, el lenguaje del programador y el de la máquina
Definición de un lenguaje de programación (desde el punto de vista del usuario)
Un lenguaje de programación es una notación que sirve para escribir algoritmos que puedan ser
ejecutados en una máquina; una notación precisa, rigurosa y con la pretensión de ausencia de
ambigüedades.
La anterior definición aporta poca información para su análisis.
Ejemplo: Program saludo ; begin writeln (“Bienvenidos a la asignatura de TLP”) end.
Definición de un lenguaje de programación (desde el punto de vista del análisis)
Un lenguaje de programación es aquél que tiene: un léxico, una sintaxis y una semántica.
La anterior definición aporta cierta información para conocer su análisis, que veremos seguidamente.
Program ejemplo; var a: integer; x: real; begin read ( a); x:= a*2 ; write (x) end .
Un lenguaje tiene un conjunto de componentes elementales e indivisibles: variables, literales,
palabras reservadas, símbolos separadores, símbolos aritméticos,..etc que pueden formar parte de un
programa fuente escrito en ese lenguaje.
Al conjunto de componentes básicos e indivisibles que se pueden usar en el lenguaje, se le llama
léxico de ese lenguaje,{ Program, id, ´;´,…}.
Los componentes léxicos que forman el léxico de un lenguaje, se pueden asociar para formar
estructuras: una asignación, una expresión,…etc. estructuras que pueden formar parte de un programa
fuente escrito en ese lenguaje.
Ejemplo correcto de una estructura en Pascal:
Program ejemplo; var a: integer; x: real; begin read ( a); x:= a*2 ; write (x) end .
Al conjunto de estructuras que permite el lenguaje, se denomina sintaxis del lenguaje .Para definir la
sintaxis de un lenguaje se utilizan las gramáticas. Una gramáticas es un conjunto finito de reglas a
través de las cuales se pueden generar el conjunto(por lo general infinito) de estructuras que forman la
sintaxis de un lenguaje.
El conjunto de estructuras que se pueden utilizar en un lenguaje deben de cumplir una serie de
restricciones y condiciones entre los elementos del lenguaje que la componen: la compatibilidad de
tipos en la estructuras de asignación, expresiones,….
El conjunto de condiciones o restricciones que deben cumplir las estructuras se llama semántica
estática
Program ejemplo; var a: integer; x: real; begin read ( a); x:= a*2 ; write (x) end .
En Pascal- una variable debe estar definida antes de usarse, debe existir una compatibilidad de tipos
entre los objetos que intervienen en una estructura ( es un lenguaje fuertemente tipado),….
El conjunto de estructuras que se pueden utilizar en un lenguaje, determinan una serie de acciones a
realizar, en una asignación: evaluar el valor la parte derecha del operador de la igualdad y asignárselo
a la memoria definida por la variable de la parte izquierda, etc….
Las acciones que debe realizar la máquina para acometer el significado de las distintas estructuras se
llama semántica dinámica.
1
- Ejemplo anterior: obtener un valor entero de la entrada y ponerlo en la variable a,
- El valor que tiene la variable a multiplicado por la constante 2, y el resultado se deja en la
variable x
- Escribir el contenido de la variable x
Cuanto mayor sea la potencia (significación) de las estructuras del lenguaje, mayor será la diferencia
de la semántica del lenguaje de programación y el lenguaje de la máquina donde se quiere ejecutar.
La forma de representar una sentencia switch del lenguaje C++, es bastante diferente y más simple
que la representación en lenguaje máquina.
Nivel de un lenguaje
El nivel (semántico) de los lenguajes es una medida indicativa de la complejidad del significado de
los programas que pueden escribirse con ellos: se refiere al nivel de complejidad de las estructuras
(construcciones) del lenguaje; suelen considerarse dos categorías más o menos genéricas:
- lenguajes de alto nivel: C++, Java, Ada, Pascal
- lenguajes de bajo nivel: lenguajes ensambladores y máquinas de diferentes máquinas
Lenguaje máquina – conjunto de instrucciones de una máquina particular que son interpretadas por el
hardware o microprogramas de la propia máquina. El conjunto de instrucciones están escritos en
notación binaria. Los lenguajes máquina, son los lenguajes de menor nivel semántico.
Lenguaje ensamblador- es una versión simbólica del lenguaje máquina. En donde las operaciones,
direcciones de memorias, registros.. se dan de forma simbólicas, con ciertas ayudas a la depuración.
Lenguajes de alto nivel – Lenguajes que están por cima de máquina y en principio son independientes
de la máquina sobre la que se van a ejecutar los programas escritos en la misma.
La separación semántica
Separación semántica es la diferencia de nivel semántico que se da entre un algoritmo escrito en un
lenguaje de programación (de un cierto nivel) y las instrucciones máquina mediante las que ese
algoritmo se ejecuta en una máquina (el nivel semántico mínimo para esa máquina). Para que un
algoritmo escrito en un lenguaje (de determinado nivel) sea ejecutable en una máquina hay que
resolver la separación semántica relativa a dicho lenguaje.
Un ejemplo puede ilustrar fácilmente la diferencia: la semántica (el significado) de la sentencia
cíclica for de Java es mucho más compleja (de significado más complicado, de mayor semántica, más
costosa de ejecutar, más difícil de explicar) que la instrucción máquina que permite almacenar un
valor constante en un registro de uso general de la unidad central de proceso. También puede hablarse
de lenguajes de nivel intermedio. Hay lenguajes que tienen, a la vez, unas características que pueden
considerarse de alto nivel y otras de nivel bajo o intermedio.
lenguaje fuente (de cierto nivel)
▫ sentencias del lenguaje ▫
algoritmo
[ significado común ]
separación
semántica
□
nivel de la máquina (física)
▫ instrucciones ejecutables en la máquina ▫
Procesador de lenguajes
Se puede definir un procesador de un lenguaje de programación (ya comentado anteriormente)
como un sistema que resuelve la separación semántica de los programas escritos en ese lenguaje; un
procesador está formado por uno o más programas interrelacionados que hacen posible que un
algoritmo (programa) codificado (escrito) en el lenguaje pueda llegar a ejecutarse en una máquina
(física).
Tipos de procesadores de lenguajes
Traductores, Intérpretes.
2
Traductor
Un traductor entre dos lenguajes de programación, es un programa que realiza una transformación
(una traducción) entre ellos: el lenguaje de entrada al traductor (lenguaje fuente) y el de salida del
traductor (lenguaje destino); el traductor acepta un texto escrito en el lenguaje fuente
(representativo de un determinado algoritmo/programa) y emite como salida una representación de
ese mismo algoritmo/programa codificada en el lenguaje destino. El traductor está escrito en un
lenguaje de implementación Li , o también llamado host.
El proceso de traducción no altera el significado del programa traducido. El traductor analiza el
programa de entrada verificando que está correctamente codificado según la definición del lenguaje
fuente; si el programa analizado no tiene errores se puede traducir; en caso contrario, el traductor
avisa de los errores detectados. La entrada al traductor se denomina programa fuente y la salida
programa destino (o también código generado por el traductor).
-- Traductor entre los lenguajes L y L’
P/L
T [Li]
P’/L’
M
E
P programa escrito en L (lenguaje fuente)
P' programa escrito en L’ (lenguaje destino)
E errores (si existen y son detectados)
M máquina donde se ejecuta el traductor
Li lenguaje de implementación de T
[ P y P' representan idéntico algoritmo ]
Lo más frecuente y razonable es que un proceso de traducción disminuya el nivel semántico: el
nivel de lenguaje destino es menor que el nivel del lenguaje fuente.
Tipos de traductores
Compiladores, ensambladores, conversores,..
Compilador
Un compilador, traduce un programa escrito en un lenguaje fuente de alto nivel a otro
semánticamente equivalente escrito en un lenguaje objeto, lenguaje que puede ser máquina o de
bajo nivel
El resultado de la compilación es un programa ejecutable en una determinada máquina
Puede afirmarse que la compilación directa a código máquina directamente ejecutable, no suele
darse con frecuencia debido a que tras de una compilación suelen aparecer acciones (enlazador,
cargador,..) para obtener el código ejecutable. La ejecución del programa compilado puede
considerarse como un proceso de interpretación del procesador.
La ejecución, es un proceso posterior en el que se ejecuta el programa objeto, teniendo un conjunto
de datos de entrada y produciendo unos resultados como salida
El siguiente esquema ilustra el proceso de ejecución de un programa compilado, con sus dos partes
bien diferenciadas: la compilación del programa fuente (ejecución del compilador) y la ejecución
del programa resultado de la compilación.
-- compilador del lenguaje
C
M
P[O]
P[O]
M'
D
R
P[L]
C compilador del lenguaje L, para la máquina M
P [ L ] programa (fuente) escrito en L
P [ O ] programa (código) objeto resultante de la traducción (compilación)
3
D datos para una ejecución del programa P
R resultados de una ejecución del programa P
Máquina anfitriona - máquina donde se ejecuta el compilador ( M )
Máquina destino- máquina para la que genera código el compilador ( M' )
El tiempo que se tarda en traducir un texto de un lenguaje se llama tiempo de compilación.
El tiempo que tarda en ejecutarse el texto de un lenguaje se llama tiempo de ejecución.
Un compilador es un programa que traduce un único lenguaje fuente, produciendo código para una
única máquina destino.
Fases de un compilador. Representaciones intermedias.
La tarea realizada por un compilador es muy compleja y por ello conviene considerarla
descompuesta en diferentes partes. Un enfoque tradicional (y bastante provechoso) en el análisis del
problema de compilar un programa escrito en un lenguaje de alto nivel es el denominado modelo
analítico-sintético.
Se considera una descomposición funcional del compilador en diferentes fases. En una primera
aproximación se diferencian dos fases: análisis y síntesis; en la fase de análisis se comprueba que el
programa está bien codificado de acuerdo con la definición del lenguaje fuente y se extrae el
significado del texto analizado; en la fase de síntesis se transcribe ese significado empleando la
notación del lenguaje destino, generando el código máquina que representa el mismo algoritmo que
el programa fuente.
Para separar claramente estas dos fases conviene desde un punto de vista conceptual considerar que,
como resultado de la fase de análisis se obtiene una representación intermedia del programa
analizado que refleja las operaciones que el programa ha de realizar y el orden en que deben de
llevarse a cabo; la naturaleza de esta representación intermedia depende del método empleado para
la implementación del compilador
P[F]
Fase de
análisis
Representación
intermedia
P[I]
Fase de
Síntesis
P[O]
P[ F ] programa P escrito en el lenguaje fuente
P[ O ] programa P escrito en el lenguaje destino
P[ I ] representación intermedia del programa P
La representación de lenguaje intermedio es el lenguaje de una máquina abstracta ( comentada mas
adelante )usado como interfaz entre el análisis y la generación de código.
La representación intermedia no es obligatoria en las fases de un compilador, puesto que un
compilador puede generar código objeto directamente (compilador puro).
Análisis: la fase de análisis debería ser independiente de la máquina, mientras que la de síntesis
depende de la máquina en la que se ejecutará.
Síntesis: A partir del código intermedio generado en la fase de análisis, generan y optimizan el
código, dependiendo ahora de la máquina en la que se ejecutará
En una segunda aproximación, la fase de análisis se considera, a su vez, descompuesta en tres fases
cada una de ellas encargada de examinar las diferentes características del programa analizado:
- el análisis lexicográfico encargado de comprobar las características lexicográficas (la forma en que
están codificados los componentes elementales del texto),
- el análisis sintáctico dedicado a verificar la estructura sintáctica de la secuencia de componentes
léxicos que les pasa el análisis anterior, y que constituyen el programa,
- el análisis semántico que realiza las comprobaciones de las restricciones de semántica estática que
han de cumplirse para que el programa analizado tenga un significado correcto.
En cualquiera de la fases de análisis pueden encontrarse errores: lexicográficos, sintácticos,
semánticos; en el momento en que se encuentra un error se decide que ya no es necesaria la
generación de código: el programa analizado es incorrecto.
4
Durante el análisis de un programa, el compilador recopila una gran cantidad de información que es
necesario tener organizada y fácilmente accesible (para hacer las comprobaciones semánticas y para
generar el código); la principal estructura de datos donde el compilador almacena la información que
obtiene del programa analizado se denomina tabla de símbolos.
En la fase de síntesis se realiza la tarea de generar el código destino; esta tarea puede hacerse
directamente en un único paso o bien considerar que primero se obtiene el programa escrito en un
lenguaje intermedio y después se transforma el lenguaje intermedio al código máquina. Para generar
código se tiene en cuenta la semántica dinámica del lenguaje que es la que establece lo que la
máquina ha de realizar para ejecutar lo indicado por el programa fuente.
Fase de Análisis
Código fuente
Análisis léxico
Componentes léxicos
Análisis sintáctico
árbol sintáctico
Análisis semántico
Árbol anotado
Fase de Síntesis
Generación de cód. intermedio
Código intermedio
Optimización de cód. intermedio
Código intermedio
Generación de código
Código objeto
Optimización de código
Código objeto
A veces también se considera como una fase dentro de la síntesis la tarea de optimización; un
compilador puede efectuar intentos de mejora (optimización) en el código generado para que ocupe
menos espacio o para que se ejecute con mayor rapidez; estas optimizaciones pueden hacerse en
diferentes momentos: sobre la representación intermedia, sobre el código destino o sobre el lenguaje
intermedio (si se ha producido la generación en dos etapas); precisamente uno de los motivos que
justifica la generación del código en dos o más etapas es la posibilidad de hacer diferentes
optimizaciones.
Tipos de compiladores
Compilador cruzado
Se genera código en lenguaje objeto para una máquina diferente a la que se está utilizando para
compilar. Uno de los problemas que se encuentra en un nueva máquina es que alguien tiene que
escribir el primer compilador. La tarea será más cómoda de hacer en la máquina que se dispongan
herramientas software, para después trasladarla a otra.
Autocompilador
Es un compilador escrito en el mismo lenguaje que compila. Cuando se extiende entre muchas
máquinas diferentes el uso de un compilador, y éste se desea mejorar, el nuevo compilador se escribe
con el antiguo, de manera que pueda ser compilado por todas esas máquinas diferentes, y dé como
resultado un compilador más potente de ese mismo lenguaje.
Metacompilador
Es sinónimo de compilador de compiladores. Se refiere a un programa que recibe como entrada las
especificaciones del lenguaje para el que se desea obtener un compilador y genera como salida el
compilador para ese lenguaje
Decompiladores
Realizan la tarea inversa a la de los compiladores. Traduce un programa fuente en un lenguaje de
bajo nivel a otro objeto de nivel superior. En la mayoría de los casos traducen un lenguaje máquina a
un lenguaje ensamblador, también llamados desensambladores
5
Compilador incremental
Es aquel que compila un programa en el que si después se descubren errores, en vez de corregir el
programa fuente y compilarlo por completo, se compilan solo las modificaciones. Lo ideal es que solo
se recompilen aquellas partes que contenían los errores, y que el código generado se reinserte con
cuidado en el lenguaje objeto generado cuando se encontraron los errores. Sin embargo esto es muy
difícil.
Compilador con montador y cargador
Compilador de distintos módulos de forma independiente y después los enlaza.
Una de las características que diferencian a unos compiladores de otros es si tienen o no la posibilidad
de traducir una parte de un programa de manera independiente y separada de la traducción de las otras
partes que conforman el programa completo.
En el siguiente esquema se muestra un programa completo que está dividido en n partes y cada una de
ellas ha sido compilada en un proceso separado de compilación; de esta forma se han obtenido n
trozos de programa traducido que no son todavía programas ejecutables; para formar un único
programa ejecutable hay que unir las n partes obtenidas en los procesos de compilación.
Sobre los trozos de programa resultantes de las compilaciones hay dos tareas pendientes de realizar:
- la resolución de las referencias: es posible que en alguna de las partes separadas del programa se
haga referencia (se use) una entidad que ha sido definida (declarada) en otra parte distinta,
- la obtención de las direcciones definitivas para los componentes del programa considerado como un
todo, una vez que se hayan juntado las distintas partes traducidas por separado.
C
P F [1]
P O [1]
C
P F [2]
∙∙∙∙
P O [2]
P O[reu]
Montador
(Cargador)
Prog ecutable
en memoria
P O[eje]
∙∙∙∙
P F [n]
C
compilador
P O [n]
montaje
ejecución
Estas tareas las resuelve un programa auxiliar que se denomina montador o montador-cargador tal y
como se ilustra en el anterior esquema. A la salida del montador se obtiene un programa completo
que ya está preparado para su ejecución.
Hay lenguajes para los que se dispone de una serie de librerías (trozos de programa) que realizan
determinadas tareas (más o menos especializadas); en este caso el montador puede incorporar el
código de esas librerías para formar un todo con el programa previamente compilado en el que se ha
hecho uso de ellas.
Pasadas de un compilador
La descomposición en fases de un compilador se refiere al análisis del problema que se plantea al
desarrollar un compilador para un lenguaje. Una vez analizado el problema y descompuesto en tareas
más sencillas, hay que diseñar una manera de hacer la implementación.
Desde el punto de vista de la implementación se fija la cantidad de pasadas que el compilador hace
sobre el programa analizado; en cada pasada se hace un tratamiento del programa completo y se
realiza sobre él alguna parte del trabajo de la compilación; el resultado de cada pasada es el punto de
partida para la pasada siguiente, esto es, cada pasada se acerca más al resultado pretendido por el
compilador.
Pasadas – son el número de veces que se procesa una representación del programa fuente. Cada
pasada requiere: lectura del código fuente, procesamiento y almacenamiento de la información
obtenida.
6
Si todas las fases de la compilación se realizan examinando una única vez el texto de entrada,
entonces se trata de un compilador de una única pasada. Si el lenguaje tiene una semántica compleja
puede requerir que cualquier implementación de un compilador requiera más de una pasada.
El método empleado para implementar el compilador también influye en el número de pasadas
requeridas. Por ejemplo, los lenguajes que permiten que el uso de una entidad (variable, subprograma,
método…) pueda preceder a su declaración (como ocurre en Java) requieren compiladores diseñados
con más de una pasada.
Las pasadas suelen agruparse:
1ª pasada – A léxico, A sintáctico, A. semántico y generación de código intermedio
2ª “
– Generación y optimización de código
Un compilador con un número grande de pasadas hace que se necesite menos memoria para la
ejecución, pero aumenta el tiempo de ejecución al realizar más operaciones de entrada salida, se
facilita la detección de errores
Compilador de una o varias pasadas
Número de veces que hay que analizar el código fuente. Típicamente una pasada para realizar el
análisis léxico y sintáctico, otra pasada para el análisis semántico y optimización dependiendo del
lenguaje fuente y una tercera pasada para la generación de código y optimización dependiente de la
máquina
C y Pascal son lenguajes de una pasada mientras que el lenguaje modula realiza 2 pasadas
Ensambladores
Ensamblador
Un traductor cuyo lenguaje fuente es un ensamblador y el lenguaje objeto es el lenguaje máquina.
Existen ensambladores con macroinstrucciones: Macroensambladores.
Desensambladores
Caso inverso a los ensambladores, traducen de código máquina a ensamblador.
Es un caso más fácil puesto que hay una correspondencia directa entre las instrucciones ensamblador
y el código máquina.
Conversores
Traducen un lenguaje de alto nivel a otro lenguaje de alto nivel, para conseguir mayor portabilidad.
Por ejemplo en un ordenador sólo hay un compilador de PASCAL, y queremos ejecutar un programa
escrito en C; Un conversor C –> PASCAL nos solucionaría el problema
Intérprete
Un intérprete es un programa que analiza y ejecuta simultáneamente un programa escrito en un
lenguaje fuente. Un intérprete hace una simulación de la ejecución del programa de entrada en la
máquina donde se está ejecutando el intérprete.
El intérprete va considerando una tras otra las instrucciones del programa interpretado y, para cada
una de ellas, analiza su significado y procede de inmediato a realizar las tareas correspondientes,
antes de pasar a la siguiente instrucción; en particular, si en el programa se indican operaciones de
entrada/salida, el intérprete controlará su realización.
-- Intérprete del lenguaje
D (datos)
I
P/L
M
R (resultados)
E
7
P
D
R
E
M
programa escrito en L
datos para una ejecución de P
resultados de una ejecución de P
errores (si existen y son detectados)
máquina donde se ejecuta el intérprete
Tipos de intérpretes: (según la estructura interna del intérprete)
Intérpretes puros
Los lenguajes no necesitan de pseudocompilación, analizan y ejecutan sentencia a sentencia todo el
programa fuente, pueden considerarse intérpretes puros los intérpretes de comandos de la mayoría
de los sistemas operativos. La interpretación pura en los lenguajes de alto nivel no suelen contruirse
puesto que el proceso es bastante ineficaz.
Intérpretes avanzados (o normales)
Incorporan un paso previo de análisis de todo programa fuente a pseudocompilación generando
posteriormente un lenguaje intermedio que es ejecutado por el mismo.
Intérpretes incrementales
Lenguajes que no se pueden compilar directamente, existen objetos que no son conocidos en tiempo
de compilación ( Lisp, prolog, smaltalk)
Comparación entre un traductor y un intérprete
Aunque a simple vista pudiera apreciarse que las funciones de un traductor y de un intérprete son
similares (ambos aceptan como entrada un programa escrito en un determinado lenguaje de
programación y con ambos se pretende conseguir la ejecución de ese programa), sin embargo los
conceptos de traducción y de interpretación son esencialmente distintos. Pueden enunciarse
abundantes diferencias entre los procesos de traducción y de interpretación, entre las más
significativas están:
• una traducción siempre produce como salida una representación del programa analizado
(el código destino); si ese código fuera ejecutable en alguna máquina; su ejecución habría
de realizarse en otro momento (distinto del de la propia traducción)
• una interpretación simula la ejecución de un programa; dicha ejecución es controlada por
el intérprete; en una interpretación no se produce código destino
En relación a un intérprete no existe el lenguaje destino (no se genera código); aunque el concepto
de lenguaje fuente es más propio de los traductores, también puede decirse que el lenguaje para el
que se construye el intérprete es su “lenguaje fuente”.
Un intérprete necesita menos memoria, es más fácil de depurar, tiene una mayor flexibilidad a la
hora de modificar las características del lenguaje fuente, tienen una mayor portabilidad, potencian el
uso de los sistemas interactivos, la dificultad y coste de programación son menores( no necesitan
fase de generación de código, optimización, montaje y reubicación).
No son útiles con estructuras complejas, programas que trabajan en modo producción y la velocidad
es importante e instrucciones son ejecutadas con frecuencia.
En la actualidad suelen mezclarse ambas técnicas (apdo. de modalidades de separación semántica) :
la primera agiliza la fase de producción debido al desarrollo de técnicas y herramientas de
construcción de compiladores, mientras que la segunda facilita la ejecución y depuración
Se puede decir que un ordenador es un intérprete de su propio lenguaje máquina, es decir siempre
se produce un proceso de interpretación. Un intérprete puede relacionarse a nivel de software o
hardware
Lenguajes intermedios
Para acercar la separación semántica de un programa escrito en un lenguaje de alto nivel a lenguaje
máquina, puede emplearse una descomposición del problema apoyada en la utilización de un
lenguaje intermedio.
8
Un lenguaje intermedio es una notación que permite representar algoritmos (secuencias de
operaciones) y que recibe ese nombre por tener un nivel semántico medio situado entre el alto nivel
del lenguaje fuente y el bajo nivel del lenguaje destino (máquina).
Puede decirse que un lenguaje intermedio es una representación más abstracta y uniforme que un
lenguaje máquina concreto.
Con el uso de un lenguaje intermedio, el primer paso en la resolución de la separación semántica
consiste en la traducción del programa escrito en el lenguaje fuente (alto nivel) al lenguaje
intermedio; aunque el programa resultante de esa traducción todavía no es directamente ejecutable
en una máquina, se ha conseguido disminuir la separación semántica entre el lenguaje fuente y la
máquina destino.
Leng. Fuente
Separación semántica entre lenguaje fuente e intermedio
Leng. Intermedio
Separación semántica entre lenguaje intermedio y máquina
Leng. máquina
Los lenguajes intermedio no son lenguajes de programación de ninguna máquina real, sino que
corresponde a una máquina abstracta.
Diseño de los lenguajes intermedios:
- Lenguajes intermedios de alto nivel - lenguajes diseñados para actuar en las primeras fases de
traducción (análisis) o incluso de procesado. Son dependientes del lenguaje fuente e
independiente de la arquitectura destino.
Las tareas de dicho lenguajes – comprobación de tipos, generación de código y optimización de
código con independencia de la plataforma.
Las representaciones de lenguajes intermedios de alto nivel mas empleados son: árboles de
sintaxis abstracta AST, grafos dirigidos acíclicos GADs y grafos de dependencia.
Facilitan la traducción del lenguaje fuente; sus características están “próximas” al lenguaje
fuente; por ejemplo el código P definido para favorecer la implementación del lenguaje Pascal,
- Lenguajes de medio nivel válidos para representar un conjunto amplio de lenguajes fuente ( no
siendo dependientes de uno concreto), válidos para representar un conjunto extenso de
arquitecturas hardware.
Representaciones mas empleadas: máquinas de pila (representación más empleada de código
intermedio) , de tres direcciones(tercetos), de cuatro direcciones (cuartetos) y notación polaca
inversa (recorrido post orden)
- leguajes intermedios de bajo nivel – permiten traducir a distintos micros, de una misma
arquitectura (dependientes de ésta), se basan en lenguajes con registros simbólicos Influidos
por la máquina destino, están “próximos” al lenguaje máquina.
Lenguaje1
Lenguaje2
Lenguajen
Traducción
directa
n lenguajes
n*m
Plataforma1
Plataforma2
P Plataforma
O [1]
m
m Plataformas
Traducciones de n lenguajes a m plataformas m*n
9
En la anterior representación puede verse que para n lenguajes sobre m plataformas se necesitan
n*m traducciones
Ventajas del lenguaje intermedio
Portabilidad de las aplicaciones: El código intermedio, al ser independiente de la plataforma puede
ser ejecutado en cualquiera que tenga disponible una maquina virtual
Adecuación de paradigmas complejos: Determinados paradigmas de programación son
complicados de traducir a código binario dado su bajo nivel de abstracción, son traducidos a código
intermedio
Distribución de aplicaciones por internet: Los módulos de aplicaciones web se pueden distribuir a
través de un servidor. Cualquier módulo de Internet dispone de una máquina virtual para cod.
intermedio
Intercomunicación de aplicaciones: La representación binaria de datos en distintas plataformas
varía. El empleo de una misma maquina abstracta evita tener que traducirlos
Inconvenientes
Introduce en el compilador una nueva fase de traducción.
Perdida de eficiencia no permite una compilación de una pasada
Dificultad para definir un lenguaje intermedio adecuado en el compromiso entre lenguaje fuente y
lenguaje máquina.
Portabilidad
Lenguaje1
C
Lenguaje2
PASCAL
Lenguajen
JAVA
n Lenguajes
Compilación
a máquina
abstracta
n+m
Lenguaje
intermedio
Traducción
a máquina
real
Plataforma1
Intel
Plataforma2
Motorola
Plataforman
Dec-Alpha
m Plataformas
En la anterior representación puede verse, que con un lenguaje intermedio las traducciones se reducen
de m*n a n+m
El código intermedio de un compilador suele ser código del lenguaje de una máquina abstracta.
Máquina diseñada sin la intención de ser implementada a nivel de hardware
Máquinas virtuales
Una máquina abstracta puede definirse como un modelo teórico para ejecutar un conjunto de
instrucciones en algún lenguaje formal (no requiere implementación hardware, en tal caso se estaría
hablando de una máquina concreta).
Una máquina virtual es una máquina abstracta para la que existe un intérprete. También puede decirse
que, una máquina virtual es una implementación software de la especificación de una máquina
abstracta
El lenguaje de la máquina virtual suele tener la función de lenguaje intermedio.
Una máquina virtual se puede implementar sobre una máquina física; para ello ha de tenerse:
- una estructura de datos que soporte la arquitectura de la máquina virtual, organización de
memoria (pila, registros,…)
- un intérprete del lenguaje de la máquina virtual que se ejecute en la máquina física.(conjunto de
instrucciones que pueden ejecutarse.
10
Hay una gran variedad de máquinas virtuales: unas basadas en pila, otras en registros o una
combinación de ambas; diseñadas para facilitar la implementación de lenguajes y para favorecer su
portabilidad a distintas máquinas físicas; por ejemplo:
- máquina virtual de java JVM (Java Virtual Machine)
- máquina P para Pascal,
- máquina A para Ada,
- máquina EM,
- …..
Los lenguajes intermedios facilitan la portabilidad de diferentes lenguajes de programación de alto
nivel a distintas máquinas (físicas). Si se quiere implementar un lenguaje de alto nivel en diferentes
máquinas, se puede desarrollar un único traductor del lenguaje fuente a un lenguaje intermedio y
varios traductores (o intérpretes) del lenguaje intermedio a cada uno de los lenguajes máquina de las
distintas máquinas (físicas).
Por ejemplo, el lenguaje de la máquina virtual de Java (bytecode) facilita la implementación del
lenguaje Java en diferentes máquinas físicas: se tiene un único traductor (compilador) de Java al
lenguaje de bytecodes y una colección de intérpretes de bytecode (un intérprete por cada máquina
física en la que se quieran ejecutar). El código bytecode se está convirtiendo en un código intermedio
universal, ya existen intérpretes de JVM (máquina virtual de java) para todos los sistemas operativos.
De ahí el famoso axioma que sigue a Java: "escríbelo una vez, ejecútalo en cualquier parte",
o "Write once, run anywhere".
Lenguaje1
Lenguaje2
PLenguajen
O [1]
N lenguajes
Compilación a
máquina
abstracta
N+m
Lenguaje
intermedio
Traducción
a máquina
real
Intérprete
LI para P1
Plataforma1
Intérprete
LI para P2
Plataforma2
P Intérprete
O [1]
LI para P2
Plataforman
Máquinas
Virtuales
M Plataformas
En lugar de traducir el código intermedio a código objeto de la plataforma destino, podemos utilizar
una máquina virtual para cada plataforma El principal objetivo es reducir el número de programas
necesarios para construir un traductor portable que permita generar código sobre una gran variedad
de plataformas
Arquitectura front-end/back-end
Con frecuencia, las fases se agrupan en una etapa inicial (Front-End) y una etapa final (Back- End).
La etapa inicial comprende aquellas fases, o partes de fases que dependen principalmente del
lenguaje fuente y que son en gran parte independientes de la máquina objeto. Ahí normalmente se
introducen los análisis léxicos y sintácticos, la creación de la tabla de símbolos, el análisis semántico
y la generación de código intermedio. La etapa inicial también puede hacer cierta optimización de
código e incluye además, el manejo de errores correspondiente a cada una de esas fases.
La etapa final incluye aquellas partes del compilador que dependen de la máquina objeto y, en
general, esas partes no dependen del lenguaje fuente, sino sólo del lenguaje intermedio. En la etapa
final, se encuentran aspectos de la fase de optimización de código además de la generación de código,
junto con el manejo de errores necesario y las operaciones con la tabla de símbolos.
11
Front - end
Código fuente
Back-end
Análisis léxico
Comp. léxicos
Generación de código
Análisis sintáctico
Árbol sintáctico
Arquitectura
Front- end /
Back-end
Optimización de código
Análisis semántico
Análisis sintáctico
Árbol anotado
Código objeto
Generación de cód. int.
Código intermedio
Optimización de cód. int
Se ha convertido en rutina el tomar la etapa inicial de un compilador y rehacer su etapa final asociada
para producir un compilador para el mismo lenguaje fuente en una máquina distinta.
Otro motivo que justifica la división de la generación de código en dos etapas (intermedio y objeto) es
la portabilidad: se puede construir una única parte frontal que sirva para traducir el lenguaje fuente a
un determinado lenguaje intermedio y varias partes terminales que generen código traduciendo ese
lenguaje intermedio a los lenguajes máquinas de distintas máquinas.
Front- end Para el lenguaje C
Lenguaje intermedio
Back-end – Dec-Alpha
Back-end – Motorola
Back-end – Intel
Cod. maq – Dec-Alpha
Cod. maq – Motorola
Cod. maq – Intel
Compilador C para tres máquinas diferentes.
También resulta tentador compilar varios lenguajes distintos en el mismo lenguaje intermedio y usar
una etapa final común para las distintas etapas iniciales, obteniéndose así varios compiladores para
una máquina.
Pascal
Java
Front - End
Front -End
C
Front
End
Back End
Cod. maq – Intel
Tres compiladores ( pascal, java y C) para una misma máquina
12
La representación intermedia actúa como medio de comunicación entre el front-end y back-end.
- Si se cambia el lenguaje fuente, se reescribe el front-end.
- Si se cambia la máquina objeto, entonces se reescribe el back-end
- Si aparece una nueva arquitectura, basta con desarrollar un traductor del lenguaje intermedio a
esa máquina
Modalidades de procesadores.
Rara vez se realiza el proceso de traducción o interpretación (pura) para resolver el problema de
separación semántica, en la mayoría de los casos coexisten, dándose primero la traducción del fuente
a una representación intermedia, a la cual se le aplica el proceso de interpretación.
Todo lenguaje de programación necesita de uno o más procesadores de lenguajes. Cada máquina
física requiere de un código específico lenguaje objeto (x 86)
Para resolver el problema de la separación semántica pueden desarrollarse distintas modalidades de
procesadores de lenguajes.
* Un compilador
Un copilador, por sí mismo constituye un procesador de un lenguaje de alto nivel; en el
proceso de compilación se obtiene el código directamente ejecutable en la máquina ( Pascal,
C, ADA).
n1
Compilador
n2
n1
n2
nivel de L (lenguaje de alto nivel)
nivel de la máquina (física)
* Dos traductores
Un traductor del lenguaje fuente a un lenguaje intermedio y otro traductor del lenguaje
intermedio al lenguaje máquina.
n1
Traductor
n2
n1
n2
n3
nivel de L (lenguaje de alto nivel)
nivel del lenguaje intermedio
nivel de la máquina (física)
Traductor
n3
* Un intérprete
Aunque no es habitual su utilización (por cuestiones de eficacia), puede considerarse un
proceso de interpretación directa (pura) de un lenguaje de alto nivel; en este caso el propio
intérprete constituiría un procesador para el lenguaje de alto nivel.
n1
Intérprete
n2
n1
n2
nivel de L (lenguaje de alto nivel)
nivel de la máquina (física)
13
* Un traductor y un intérprete
Un traductor del lenguaje fuente a un lenguaje intermedio y un intérprete del lenguaje
intermedio (java, Perl, PHP).
n1
Traductor
n2
n1
n2
n3
nivel de L (lenguaje de alto nivel)
nivel del lenguaje intermedio
nivel de la máquina (física)
Intérprete
n3
Herramientas para la construcción de procesadores de lenguajes
A parte de las herramientas de desarrollo de software convencionales como: editores, depuradores,
perfiladores,…, para la construcción de procesadores de lenguajes, existen otras herramientas
especializadas y cada vez más, que ayudan a la construcción de procesadores, entre los que podemos
destacar los siguientes:
Generadores de analizadores léxicos
Generación basada en el uso de expresiones regulares, generan automáticamente el código fuente
para el análisis léxico a partir de una especificación de los tokens. El generador es un autómata finito.
El generador de nuestro estudio es JavaCC que genera código en java.
Generadores de analizadores sintácticos
Construyen el código fuente del analizador sintáctico a partir de la especificación de la gramática del
lenguaje fuente. El generador de nuestro estudio es JavaCC que genera código en java.
Analizadores de gramáticas
Dada una gramática especificada formalmente, verifican si es de un determinado tipo o no.
Normalmente se utilizan para verificar las gramáticas LL(k) y LR(k).
Máquinas de traducción dirigida por sintaxis
Producen un conjunto de rutinas que recorren el árbol sintáctico y generan código intermedio.
Asocian una o más traducciones a cada nodo sintáctico.
Generadores automáticos de código
Trabajan con un conjunto de reglas que permiten la traducción del código escrito en lenguaje
intermedio al lenguaje objeto. Las reglas suelen remplazar instrucciones de código intermedio por
patrones que contienen las instrucciones equivalentes de la máquina objeto.
Analizadores de flujo
Suministran la información necesaria para realizar las optimizaciones de código.
Entorno de desarrollo integrado
Conjunto de aplicaciones que permiten la escritura de los procesadores: editor, compilador,
depurador, enlazador…
Herramientas relacionadas con los procesadores de lenguajes
Las técnicas empleadas en la construcción de traductores, compiladores e intérpretes pueden aplicarse
en la construcción de otras herramientas:
Editores sensibles al contexto
Avisan al programador de posibles errores sintácticos cuando está escribiendo un programa fuente.
Actualmente es muy común editores con sintaxis resaltada con colores,
Conversores de formato
Utilizan las técnicas de los traductores para convertir una descripción de ficheros en otra.
14
Preprocesadores
Toman como entrada un conjunto de instrucciones y generan código en un lenguaje de alto o medio
nivel.
Formateadores de código fuente
Toman como entrada un código fuente y obtienen a la salida el mismo mostrado de manera que se
pueda seguir la estructura del programa.
Generadores de código
Permiten desarrollar aplicaciones a partir de unas especificaciones muy compactas, que pueden ser
tratadas como un lenguaje de aplicación. Un caso particular son los generadores de pantallas.
Verificación estática de programas
Leen el código fuente y lo analizan para descubrir errores potenciales sin ejecutar dicho programa.
Formateadores de texto
Reciben como entrada un texto con indicaciones de cómo se desea la salida y generan dicho texto
formateado en un fichero, o para una determinada impresora. Pueden estar especializados para
fórmulas matemáticas, químicas, música, etc.
Intérpretes de comandos de un sistema operativo
Reciben órdenes del sistema operativo, las analizan y las ejecutan ( Ej.: COMMAND.COM de MSDOS).
Construcción de entornos operativos
Caso particular del anterior en el cual las órdenes suelen recibirse de forma gráfica ( Ej. WINDOWS).
Intérpretes para consultar base de datos: reciben las consultas, las analizan y las ejecutan (EJ.:
SQL).
Compiladores de silicio
Utilizan las mismas técnicas de construcción de compiladores e intérpretes pero implantadas en
hardware.
Procesamiento de lenguajes naturales
Aplican las técnicas de construcción de traductores a los lenguajes naturales, permitiendo el análisis
comprensión y traducción (inteligencia artificial).
Reconocimiento del habla
Se realiza un análisis de los sonidos para construir palabras (redes neuronales)
Tipos de lenguajes de programación
Los lenguajes de programación se pueden clasificar desde diferentes puntos de vista:
Según el grado de independencia de la máquina (nivel):
Lenguaje máquina
Representa la forma más baja de un lenguaje de programación., escrita en la notación que entiende
directamente un ordenador binario o hexadecimal. Se basa en la arquitectura de la máquina de Von
Neumann.
Lenguaje ensamblador
Representa una versión simbólica de un lenguaje máquina. Cada código de operación se indica por un
código simbólico: ADD, MUL...Asignaciones de memoria se dan con nombre simbólicos
Lenguaje de nivel intermedio (caso del lenguaje C)
Al ser un nivel intermedio dispone de características de los de bajo y alto nivel
Características de los lenguajes máquina: Acceso directo a posiciones memoria. Almacenan variables
en registros del procesador..
Características de lenguajes de alto nivel: Manejo de estructuras de control, Manejo de datos
Lenguaje de alto nivel
Características superiores a los lenguajes ensambladores
No tienen acceso directo al sistema, estructuras de pueden ser datos complejas, utilización de bloques,
procedimientos o subrutinas, (smalltalk, C++, Ada, Java, turbo Pascal…)
Lenguajes orientados a problemas concretos
Resolución de problemas en un campo específico (SQL, XBASE, PostScript, SPSS)
15
Según la forma de las instrucciones (características intrínsecas al lenguaje:
Lenguajes imperativos o procedurales
Orientados a instrucciones o sentencias, hacen uso masivo de variables, incorporación de mecanismos
de bloques. Están Influidos por la máquina de Von Neumann. Programación estructurada ( Pascal,
C++, COBOL..).
Lenguajes declarativos: lógicos y funcionales
Lenguajes de muy alto nivel con notación muy próxima al problema real del algoritmo que
resuelven.
- Funcionales: sus construcciones son llamadas a funciones. No hay instrucciones. Todo el programa
es una función y todas las operaciones son funciones más simples. En la ejecución se aplica la
función a datos de entrada (argumentos) y se obtiene el resultado calculado por la función (LISP).
- Lógicos: instrucciones se forman siguiendo un tipo de lógica, maneja relaciones (predicados) entre
objetos (datos). La relaciones se especifican con reglas y hechos. La ejecución consiste en
demostraciones de hechos sobre las relaciones mediante preguntas (PROLOG).
Lenguajes concurrentes: Dos o más tareas simultaneas o paralelas.
Pueden ser una característica propia del lenguaje o el resultado de ampliar las instrucciones de un
lenguaje no concurrente: Ada, Concurrent C, concurrent Pascal.
Lenguajes orientados a objetos:
Basados en la teoría de los objetos que permitiendo: la encapsulación, herencia y polimorfismo ( java,
C++, smalltalk…).
Según la generación:
Primera generación: Lenguajes máquina y ensamblador en los años 50
Segunda generación: Lenguajes con asignación estática de memoria ( en tiempo de compilación).
No manejan recursividad ni estructuras dinámicas de datos en los años 60. (FORTRAN, COBOL).
Tercera generación: Programación estructurada en los años 70. Uso de subprogramas o módulos,
variables locales, recursividad y estructuras dinámicas.(Algol, PL/I, PASCAL, MODULA).
Cuarta generación: De muy alto nivel para tareas específicas a primero de los años 80. Base de
datos, herramientas CASE
Quinta generación: Inteligencia artificial (LISP PROLOG)
Generación orientada a objetos:
Con la proliferación de las interfaces gráficas de usuarios en los años 90
Generación visual:
Exigencia de los usuarios de interfaces amigables en los años 90 (Visual Basic, Delphi)
Generación Internet:
Necesidad de manejar aplicaciones en diferentes plataformas dentro de internet (Java, XMN, HTML,
VRML)
Otros lenguajes
En informática también se utilizan otros lenguajes que no son de programación y que tienen otras
aplicaciones, como pueden ser para describir formatos de texto, gráficos, de sonido, etc. En cualquier
caso, todos los lenguajes no naturales son formales, surgen primero las reglas gramaticales y se
ajustan con todo rigor a ellas. Gracias a ello se pueden construir procesadores ( término genérico que
hace referencia al proceso de transformación)
Tipos de lenguajes que no son de programación:
De descripción de páginas: Postcript, True Page....
De formatos gráficos no vectoriales: TIFF, GIF, PCX, BMP, JPEG....
De formatos gráficos vectoriales: DXF, CGM....
De formatos de bases de datos: DBF, DBT, MDX....
De formatos de texto: RTF, ASCII, Word, WordPerfect....
De formatos de archivos de sonido: WAV, MIDI, MP3....
De formatos de archivos de vídeo: AVI, MOV,...
De formatos de ayuda: HLP de Windows, HLP de Turbo Visión...
De gestión electrónica de documentos e hipertexto: pdf de Acrobat, HTML, XML.....
16
TEMA 2 LENGUAJES FORMALES
2.1.- Conceptos básicos sobre palabras.
Lenguaje formal - Un lenguaje formal es un conjunto de palabras definidas sobre un alfabeto.
Alfabeto - Se llama alfabeto a un conjunto finito, no vacío de símbolos. Se representan por (Σ).
Un alfabeto se define por la enumeración de los símbolos que lo contiene.
Ejemplos de alfabetos: Σ1 ={a,b}, Σ2={0,1}, Σmorse ={.- , .-- ,..}, ΣASCII ={A, %, …}
ΣHTML ={<HTML>, <BODY>, A..Z, a ..z,.}, Σ=Conjunto de palabras de un diccionario
Si Σ1 y Σ2 son alfabetos, Σ1 ∪ Σ2 es un nuevo alfabeto.
Si Σ1 y Σ2 son alfabetos, Σ1 ∩ Σ2 es un nuevo alfabeto, siempre que Σ1 ∩ Σ2 ≠φ
Símbolo - Un símbolo es un componente mínimo e indivisible que puede formar parte de una palabra.
Un símbolo es una entidad abstracta que no tiene definición. Los símbolos pueden estar formados por
más de un componente.
a ∈ Σ1,, −.− ∈ Σmorse, <HTML>∈ΣΗΤΜΛ
Cadena, palabra - Una palabra es a una secuencia finita de símbolos de un alfabeto. Las palabras se
representan por los últimos símbolos del alfabeto castellano en minúsculas x.y,z,..
x(∑1)=abab
∀ a ∈ Σ, a es una palabra definida sobre Σ .
Cualquier programa (p.e en Pascal) será una cadena sobre el alfabeto de caracteres ASCII
Longitud de una palabra x - La longitud de una palabra es el conjunto de símbolos que tiene esa
palabra, se representa por |x|
x =aaaabb ; |x| = 6
Palabra vacía - es una palabra que no tiene símbolos, se representa por ε (épsilon), y su longitud es
0, x=ε ; |x|=0.
Un alfabeto Σ, nunca tiene el símbolo vacío ε.
Σ = {a, b}, Σ*={ε, a, b, ab, ba, aa, bb, aaa........}, Σ ⊆ Σ*
Sea el alfabeto Σ, Σ* es el conjunto de todas las palabras que se pueden formar con Σ.
Sobre cualquier alfabeto Σ , Σ* es infinito. Σ* se denomina lenguaje universal definido sobre Σ.
Subpalabra - Sea una palabra x definida sobre Σ; subpalabra de x es una nueva palabra y, definida
sobre el mismo alfabeto, formada por símbolos de x entrescados de forma consecutiva de ésta.
Sean x, y, z, v ∈ Σ*, y es subpalabra de x si ∃ z, v ∈ Σ* | x = v·y·z
Sea x=aab, donde Σ={a, b}, subpalabras de (x) = {ε, a, aa, b, ab, aab}
Subpalabra propia - x es una subpalabra propia de y ⇔ existen z,v siendo alguna de ellas no vacía
tales que vxz=y
Prefijo de una palabra, es una subpalabra entresacada desde el inicio de dicha palabra.
Sean x, y, z ∈ Σ*, y es prefijo de x si ∃ z ∈ Σ* | x = y·z
Sea x=aab, donde Σ={a, b} , prefijos(x) = {ε, a, aa, aab}
Prefijo propio - y es prefijo propio de x si x =y · z, si ∃ z ∈ Σ+ , z∉ε | x = y·z
Sufijo de una palabra, es una subpalabra entresacada desde el final de dicha palabra.
Sean x, y, z∈ Σ*, y es sufijo de x si ∃ z ∈ Σ* | x =z · y
Sea x=aab, donde Σ={a, b}, sufijo de (x) = {ε, b, ab, aab}
Sufijo propio - y es sufijo propio de x si ∃ z ∈ Σ+ , z∉ε | x = z.y
Cualquier palabra x incluyendo la palabra vacía, son prefijo y sufijo de sí misma
Subsecuencia de una palabra x, es una combinación de símbolos de dicha palabra en orden, pero no
tienen que ser consecutivos.
Sea x=abab, donde Σ={a, b}, subsecuencias de (x) = {ε, bb, ab,…..}
17
2.2.- Operaciones con palabras.
Al estar los lenguajes formales compuestos de palabras, las operaciones de lenguajes formales, serán
operaciones con palabras. Las operaciones a destacar con palabras son las siguientes:
Concatenación de palabras (.)
Sean dos palabras x,y definidas sobre el mismo alfabeto Σ, la concatenación de ambas x.y (xy) es
una nueva palabra z, formada por los símbolos x seguidos de los símbolos de y.
x(∑).y(∑)=x(∑)y(∑)=z (∑)
x=ab, y=aa ; xy=z=abaa ; |x|+|y|=|z|
Propiedades de la concatenación
∀ x, y ∈ Σ*, la operación concatenación (.) cumple las siguientes propiedades:
Operación cerrada x.y =z
P. asociativa
x(yz) = (xy)z
E. neutro
xε = εx=x
Potencia de una palabra
Dado que la concatenación permite la propiedad asociativa, una palabra x, se puede concatenar
consigo misma un número determinado n de veces, dando lugar a la potencia n-ésima de esa palabra
xn
x=aab, x0=ε , x3=aabaabaab
Propiedades de la potencia
∀ x ∈ Σ* ⇒ ∃ xn∈ Σ*
xn =
ε
si n=0
xn-1x si n>0
|xn|=n |x|
Palabra inversa (refleja, transpuesta)
Dada una palabra x definida sobre un alfabeto Σ, su inversa o refleja x-1 (xi ), es una nueva palabra
definida sobre el mismo alfabeto, formada por los mismos símbolos que tiene x pero situados en
orden inverso.
x=aab , x-1=baa
Propiedades de la inversión
∀ x ∈ Σ* ⇒ ∃ x-1∈ Σ*
x-1 =
ε
si x= ε
-1
y a si x=ay
-1
|x|=|x |
La operación inversa cumple la propiedad de idempotencia
( x-1 ) -1 = x
Una palabra es igual a otra sí tienen la misma longitud y los símbolos en la misma posición.
2.3.- Conceptos básicos de lenguajes formales
Un lenguaje formal L definido sobre un alfabeto ∑, es cualquier subconjunto de ∑*, conjunto de
palabras con una significación propia (lógica común) y, tiene una definición formalizada
L(∑) ⊆ ∑*
L(∑)={x∈∑* | x cumple con la definición formal del lenguaje}
18
Ejemplos de lenguajes formales definidos sobre el alfabetos Σ = {a,b}:
L1 = {}= ∅, lenguaje vacío Cardinal ({})=0
L2 = { ε },
lenguaje no vacío Cardinal ({ε })=1
L3 = {a, b}, el lenguaje coincide con el alfabeto
L4 = {a, ab, ε} el lenguaje contiene la palabra vacía
L5 = {an, n≥0}= {a0, a1, a2,…}={ ε, a, aa,…} lenguaje formado por palabras que contienen
cero o más aes , definido por una notación matemática
L6 ={anbm | n, m≥0}={a0b0, a0b1,… .}= {εε, εb,…}={ ε, b,…} lenguaje formado por palabras
formadas por (0 o más) aes seguidas de (0 o más) bes, definido por una notación
matemática
L7 ={palabras formadas por un número impar de aes}={ a, ba, ab, bab, aaba,…}
Σ*=lenguaje universal de Σ, o cierre de Σ . Lenguaje, formado de todas las palabras que se
pueden construir con los símbolos {a,b} sin limitación de orden y longitud, incluyendo la
palabra vacía.
2.4.- Operaciones con lenguajes.
Al ser un lenguaje formal un conjunto de palabras, se puede aplicar toda la teoría de conjuntos sobre
ellos. En éste texto vamos a estudiar las siguientes operaciones con lenguajes formales:
Sean L1 y L2, lenguajes definidos sobre Σ, L1, L2 ⊆ Σ*
L1 ={ε, a, aa, aab}, L2 ={b, bb, aa, aabb}
- Alternativa o unión de lenguajes
Dado dos lenguajes L1 y L2 definidos sobre un mismo alfabeto Σ, la unión de ambos es un nuevo
lenguaje L3 , definido sobre el mismo alfabeto Σ y formado por las palabras de L1 y L2
L1 | L2 = L1 ∪L2 = {x∈ Σ * | x∈L1 ∨ x∈L2 }
L1 ={ε, a, aa, aab} , L2 ={b, bb, aa, aabb}
L1 | L2 ={ ε, a, aa, aab, b, bb, aabb}
Propiedades que cumple la operación (|) unión de lenguajes
∀ L ⊆ Σ* se cumplen las siguientes propiedades:
Operación cerrada
P. idempotente
P. conmutativa
P. asociativa
E. neutro
L 1 | L2 = L3
L1| L1 = L1
L1| L2 = L2|L1
L1| (L2|L3) = (L1|L2)|L3
L1|φ = L1
- Concatenación o yuxtaposición
Dado dos lenguajes L1 y L2 definidos sobre un mismo alfabeto Σ, la concatenación de ambos es un
nuevo lenguaje L3, definido sobre el mismo alfabeto Σ y formado por las palabras de L1 concatenadas
con las palabras de L2
L1L2=L1· L2 = { xy ∈ Σ*| x∈L1 ∧ y∈L2 }
L1 ={ε, a, aa, aab} , L2 ={b, bb, aa, aabb}
L1. L2 ={ εb, εbb, εaa, εaabb, ab, abb, aaa, aaabb, aabb, aabbb, aabaa, aabaabb }
Propiedades que cumple la operación (.) concatenación de lenguajes
∀ L ⊆ Σ* se cumplen las siguientes propiedades:
Operación cerrada L1 L2 = L3
P. asociativa
L1 (L2L3) = (L1L2)L3
E. neutro
L1.ε = ε.L1=L1
19
La operación concatenación de lenguajes formales cumple con la propiedad distributiva con respecto
a la operación unión
L1 (L2|L3) = (L1 L2|( L1L3)
(L1|L2)L3 = (L1 L3)| ( L2L3)
P. distributiva
- Potencia de lenguajes
Dado que la concatenación de lenguajes formales permite la propiedad asociativa, un lenguaje formal
L1 se puede concatenar consigo mismo, un número determinado n de veces, dando lugar a la potencia
n-ésima de ese lenguaje Ln
Ln=LLL..n veces L
L1 ={ε,a,aa,aab}
L1L1=L12 ={εε, εa, εaa , εaab, aε, aa, aaa, aaab, aaε, aaa, aaaa, aaaab, aabε, aaba, aabaa, aabaab}
Propiedades de la potencia de lenguajes
∀ L ⊆ Σ* ∃ Ln ⊆ Σ*
L⊆ Σ * , Ln⊆ Σ *
Operación cerrada
Li .L = L.Li=Li+1
Cualquier lenguaje L0= ε
ε
Ln =
si n=0
n-1
L L si n>0
En la definición anterior existe una incongruencia para el lenguaje L= φ , φ0= ε
- L* (L estrella, cierre de Kleene)
L* es el conjunto de palabras que se pueden formar al concatenar en cualquier orden y cantidad las
palabras de L, incluyendo la palabra vacía.
∞
0
1
n
L*=L |L | …..L …= U Li
i=0
Sea L1 ={ε, a, aa, aab}
L1* ={ ε } | {ε,a,aa,aab} | {εε, εa, εaa , εaab, aε, aa, aaa, aaab, aaε, aaa, aaaa, aaaab, aabε,
aaba, aabaa, aabaab} | {….} |…
- L+ (L mas , cierre positivo de L)
L+ es el conjunto de palabras que se pueden formar al concatenar en cualquier orden y cantidad las
palabras de L
∞
L+=L1| L2…..Ln…=L+= U Li
Sea L1 ={ε, a, aa, aab}
i=1
L1+ ={ε,a,aa,aab} | {εε, εa, εaa , εaab, aε, aa, aaa, aaab, aaε, aaa, aaaa, aaaab, aabε, aaba,
aabaa, aabaab} | {….}
L*= L+| L0 ; L*= L+| {ε} ; L+= L* sii ε ∈ L ; L+=L*.L
- Σ * ( sigma estrella, lenguaje universal sobre Σ)
Σ* es el conjunto de palabras que se pueden formar al concatenar en cualquier orden y cantidad los
símbolos de Σ , incluyendo la palabra vacía.
∞
Σ*= Σ |Σ |Σ ….= U Σ i
0
1
2
i=0
Σ*=L* sii Σ⊆L ; Σ*=L+ sii Σ⊆L y ε∈L
20
- Σ + (sigma mas)
Σ+ es el conjunto de palabras que se pueden formar al concatenar en cualquier orden y cantidad los
símbolos de Σ ,
∞
Σ+= Σ1|Σ2….= U Σ i
i=0
Σ =L sii Σ⊆L y ε∉L ; Σ ≠L* ; Σ +≠ Σ*
+
+
+
- Intersección de lenguajes
Dado dos lenguajes L1 y L2 definidos sobre un mismo alfabeto Σ , la intersección de ambos L1∩L2 es
un nuevo lenguaje L3 definido sobre el mismo alfabeto Σ y formado por las palabras que pertenecen a
L1 y a L2
L1 ∩L2 = {x∈ ∑ * | x∈L1 ∧ x∈L2 }
L1 ={ε, a, aa, aab} , L2 ={b, bb, aa, aabb}
L1∩L2 ={aa}
Propiedades de la operación (.) intersección de lenguajes
∀ L ⊆ Σ* se cumplen las siguientes propiedades:
Operación cerrada L1∩ L2 = L3
P. idempotente
L1∩ L1 = L1
P. conmutativa
L1∩ L2 = L2∩L1
P. asociativa
L1∩ (L2∩L3) = (L1∩L2) ∩L3
E. neutro
L1∩ Σ *= L1
P. distributiva
L1∪ (L2∩L3) = (L1∪ L2) ∩(L1∪ L3)
L1∩ (L2∪L3) = (L1∩L2) ∪ (L1∩ L3)
- Sublenguaje de otro
Sean dos lenguajes definidos sobre un alfabeto: L1 ={ε, a, aa, aab}, L2 ={ ε, a, aa, aab, b, bb, aabb}
L1 es sublenguaje de L2 si L1 ⊆L 2
Todo lenguaje L1 definido por un alfabeto Σ, es un sublenguaje del lenguaje universal Σ *, L1⊆ Σ *
L1={ε, a, aa, aab} ⊆ { ε, a, b, aa, bb, ab, ba, aab,…..}
Dos lenguajes son iguales si todos sus elementos son iguales
- Diferencia de lenguajes formales
Dado dos lenguajes L1 y L2 definidos sobre un mismo alfabeto Σ, la diferencia de ambos L1-L2, es un
nuevo lenguaje L3 definido sobre el mismo alfabeto Σ y formado por las palabras que pertenecen a L1
y no pertenecen a L2
L1-L2 = {x∈ ∑ * | x∈L1 ∧ x∉L2 }
L1 ={ε, a, aa, aab} , L2 ={b, bb, aa, aabb}
L1-L2={ε, a, aab}
Propiedades de la operación (-) diferencia de lenguajes
∀ L ⊆ Σ* se cumplen las siguientes propiedades:
Operación cerrada L1- L2 = L3
P. idempotente
L1 - L1 = L1
E. neutro
L1- φ= L1
21
- Complemento de un lenguaje:
Dado un lenguaje L definido sobre un alfabeto Σ, su complementario L, es un nuevo lenguaje formal
definido sobre el mismo alfabeto, compuesto de todas las palabras que se pueden formar con los
símbolos de Σ (∑* lenguaje universal) menos las palabras del propio L, L= ∑* - L
__
L = { x∈ ∑ * | x∈ ∑ * ∧ x∉L}
Propiedades:
∀ L ⊆ Σ* se cumplen las siguientes propiedades:
Operación cerrada L ⊆ Σ* ;
L ⊆ Σ*
L = ∑* - L
L =L
L ∩L=φ
L U L = ∑*
- Otras propiedades: leyes de Morgan
L1∩ L2 = L1 U L2
L1 U L2 = L1 ∩ L2
- Lenguaje inverso
Dado un lenguaje L definido sobre el alfabeto Σ, su inverso L-1 es un nuevo lenguaje definido sobre
el mismo alfabeto Σ y, formado por las palabras inversas de L
L-1 = { x-1 ∈ Σ * | x∈L }
L ={ε, a, aba, aab} , L-1 ={ ε, a, aba, baa }
2.5.- Mecanismos formales
Un lenguaje formal se define por las propiedades que cumplen las palabras que lo componen. Las
propiedades que cumplen las palabras del lenguaje deben permitir representarlas de una manera
formalizada.
Si el lenguaje es finito, para su definición basta con su enumeración, conocer su representación
L= { a, aa, aaa} – lenguaje cuyas palabras están formadas por: una, dos o tres aes
Si es infinito no puede ser enumerado, habrá que buscar un medio finito y preciso para su definición.
Pero ocurre que no todos los lenguajes formales pueden definirse de una forma finita y precisa
L={ε, a, ,aa, ..ab, aab,…b, bb,… abb…. .} = L={anbm | n,m≥0}
Tiene una definición en notación matemática, pero dicha definición no aporta información para su
tratamiento
L= { a, abbaa, aabab, ababa, abbaa, aaabb,…aaaaabbbb,…….}
{palabras que tienen un número impar de aes, empiezan por el símbolo a y tienen una a más que bes}
La definición no es precisa, con las palabras anteriormente representadas, y dicha definición no aporta
información para su tratamiento
Para evitar los anteriores problemas de indefinición y tratamiento, se crearon los mecanismos
formales: expresiones regulares, autómatas y gramáticas:
Las expresiones regulares son mecanismos que sirven para describir un tipo de lenguaje formal
(lenguajes regulares).
Los autómatas son mecanismos que permiten especificar de manera finita y precisa cualquier lenguaje
formal, mecanismos que permiten simular el reconocimiento del lenguaje para dicha especificación
22
Las gramáticas son mecanismos que permiten especificar de manera finita y precisa cualquier
lenguaje formal, mecanismos permiten simular la generación de las palabras del lenguaje
especificado.
En este tema se ha hecho referencia a la definición lenguaje formal para diferenciarlo de lenguaje
natural.
En general, un lenguaje natural es aquel que ha evolucionado con el paso del tiempo para fines de la
comunicación humana, evolucionan sin tener en cuenta reglas gramaticales estrictas que puede
resolver situaciones ambiguas.
Mientras que los lenguajes formales, al contrario que los naturales, están definidos por reglas de
producción preestablecidas y se ajustan con todo rigor y formalidad a ellas.
Ejercicios sobre lenguajes formales
1 - Alfabeto de entrada para los lenguajes- java , C, Pascal
2 - En una palabra de longitud n – cuantas subpalabras hay en las definiciones: prefijos, sufijos,
prefijos propios,..
3- Demostrar que (xy)-1= y-1.x-1 ∀ x,y ∈Σ*.
4 - Dados los siguientes lenguajes formales definidos sobre el alfabeto ∑= {a, b}:
•
•
•
•
•
•
•
•
L0={}
L1={ε}
L2={a}
L3={a,b}
L4={(ab)n | 0<= n < 2 }
L5={ambl | m,l >= 0 }
L6={anbn | n>=0 }
L7 ={lenguaje formado por las palabras que tienen un número impar de aes}
Determinar para cada uno de ellos L0, L3 y L*
5- Sean las siguientes operaciones con los anteriores lenguajes formales:
L0n L1n, L0n| L1n, L3n L1n, L3n | L1n, L3n L0n, L3n| L0n, L7n ∩ L5n, L7n| L5n , L0n , L3n, , L7n
1. Determinar qué lenguajes definen suponiendo que n = 0.
2. Determinar qué lenguajes definen suponiendo que n = 3
3. Determinar qué lenguajes definen suponiendo que n = *
6- Sea ∑ un alfabeto cualquiera y L un lenguaje cualquiera definido sobre el alfabeto anterior. Para
cada una de las siguientes igualdades, justificar si siempre son ciertas o no.
L*=L+ ∑*=L+
L =∑*- L = L*-L
∑+=L+ L+=L*-ε
∑+=∑*- ε
∑*=(∑*)-1
Φ *= ε=Φ+
L1-L2 = L1 ∩ L2 = L1∪L2 (L*)*=(L+)*=(L*)+=L*=(L+)+
(L*)I=(LI)* L1- (∑*-L1)=L1 L1-∑*-L1= Φ L1-L2 = L1 ∩ L2 = L1∪L2
7 - Sea L1 un lenguaje definido sobre un alfabeto Σ ={a,b} formado por las palabras que tienen un
número impar de símbolos a y como máximo dos símbolos b consecutivos.
Ejemplos válidos: a, aaba, aaabbaba, babbabbab
Ejemplos no válidos: ε, aabbba, bb, abbabbaba
Sea L2 un lenguaje definido sobre un alfabeto Σ ={a,b} formado por las palabras que tienen un número
par de símbolos a (el 0 se considera un número par) y como máximo dos símbolos b consecutivos.
Ejemplos válidos: ε, aa, aaaba, aaabbaaba, baabbabbab, bb
Ejemplos no válidos: a, aabbbaa, aabbabbaba
Justificar si se cumplen o no las siguientes igualdades:
L1 | L2=Σ* , L1.L2=L2.L1 , L1*=Σ* , L1=L2 complementario
23
2.6.- Gramáticas Formales
Una gramática es un mecanismo formal generador de lenguajes formales, que especifica de una
manera finita y precisa el conjunto de palabras que componen el lenguaje al que se quiere definir.
Una gramática está formada por un conjunto finito no vacío de reglas (producciones) a través de las
cuales se obtiene (genera) un lenguaje.
- Definición formal
Una gramática es un mecanismo formal compuesto por cuatro elementos G = (Σ, N, S, P) en donde:
Σ - es el alfabeto terminal, alfabeto de las palabras del lenguaje que genera la gramática
N - es el alfabeto no terminal, alfabeto auxiliar que determina la complejidad de la estructura de
las palabras a generar por la gramática Σ ∩ N=Φ
S - es el símbolo inicial a partir del cual se obtienen todas las palabras del lenguaje a generar por la
gramática
S∈N
P - es un conjunto finito de reglas de producción a través de las cuales se obtienen todas las
palabras del lenguaje que genera la gramática.
P: { α1→β1, α2→β2…..} , αi ∈ ( N | Σ)* N ( N | Σ)*, βi ∈ ( N | Σ)*
Ejemplo
Sea un lenguaje formal compuesto por las palabras que se ajustan a la siguiente definición:
L(Σ )={anbm | n, m≥0}={a0b0, a0b1, a1b0, a1b1,….
Una gramática que genera dicho lenguaje es la siguiente:
G = (Σ ={a,b}, N={S,A,B},S, P)
S → AB
A → Aa |ε
B → Bb |ε
- Derivación en el entorno de una gramática
Derivación en el entorno de una gramática - es un proceso enfocado en la obtención de las palabras
del lenguaje que genera dicha gramática
P:
Sea una gramática G = (Σ, N, S, P) P: { α1→β1, α2→β2, .. } α ∈ (N | Σ)*N( N| Σ)*, β ∈ ( N|Σ)*
L(G)={ x∈ Σ* | xδG ( x deriva de G) }
- Derivación directa (derivación de un paso)
Consiste en aplicar sobre una palabra ω ∈ ( N | Σ)* N ( N | Σ)* obtenida de la gramática G, una
producción α1→β1 de la propia gramática G.
Sea la palabra ω1 = δ1α1δ2
;
δ1,δ2∈ ( N | Σ)*
y
α1 ∈ (N | Σ)* N (N | Σ)*
Aplicando la producción α1→β1 de la gramática G sobre dicha palabra ω1, la palabra ω1 se
transforma en δ1β1δ2= ω2 en donde δ1,β1,δ2∈ ( N | Σ)*
Es decir δ1α1δ2→δ1β1δ2
,
ω1→ω2
Por lo que puede decirse que la palabra ω1 produce la palabra ω2 (de ahí el nombre de producción en
la definición de gramáticas), o bien que la palabra ω2 deriva de la palabra ω1.
En un proceso de derivación de un paso o producción directa, es un proceso no determinista que sólo
se aplica a una producción, aunque dicha producción u otras de la gramática se puedan aplicar sobre
la misma palabra ω.
Una palabra se deriva o se produce así misma sin aplicar el proceso de derivación, se dice que se
deriva en cero pasos.
24
Relación de derivación
Sean las palabras ωi,…ωm ∈ (N | Σ)* N (N | Σ)* se dice que están en relación de derivación en la
gramática G, si la palabra (ωi produce la palabra ωm) o ωm deriva de la palabra ωi en cero o más
derivaciones directas.
ωi→ωj ; ωj→ωk ; ωk→ωm
La representación de un proceso de cero o más derivaciones se representa de la siguiente forma:
ωi→*ωm , de la misma manera para representar un proceso de una o más derivaciones es la siguiente:
ωi→+ωm
- Lenguaje generado por una gramática
Conjunto de palabras definidas sobre el alfabeto Σ, que se obtienen desde el símbolo inicial en un
proceso de derivación de cero o más pasos.
S→ α1 → α2 → α3….→ x = x∈ Σ*
L(G)={x | x∈ Σ* , S→*x}
Lenguaje generado por la siguiente gramática:
G = (Σ ={a, b}, N={S, A, B}, S, P)
P:
S → AB
A → Aa |ε
B →Bb |ε
AaB
S→AB
B
AbB
A
AaaB
aB
Aa
AaBb
Bb
ε
AabB
bB
AbBb
Ab
Aa
ε
…anbm | n≥1, m≥0
…. bm |m≥0
L(G)= {an bm | n≥0, m≥0}
… anbm | n≥0, m≥1
... an | n≥0
Forma sentencial – cualquier palabra obtenida de un proceso de derivación; en el anterior ejemplo
cualquier palabra derivada desde S: S , AB, ε…..aaabb, son formas sentenciales
Si la forma sentencial está compuesta únicamente por símbolos terminales de la gramática, se llama
sentencia . En el ejemplo anterior son sentencias: ε, a , b, aa,…..aaabb
A partir de la anterior definición, se puede decir que, el lenguaje generado por una gramática es el
conjunto de sentencias que pueden obtenerse desde el símbolo inicial en un proceso de derivación.
- Gramáticas equivalentes
Una gramática genera un solo lenguaje, pero un lenguaje puede ser generados por muchas gramáticas.
Dos o más gramáticas se dice que son equivalentes, si generan el mismo lenguaje.
Ejemplo: G1 = (Σ ={a, b}, N={S, A, B}, S, P)
P: S → AB
A → Aa |ε
B →Bb |ε
25
G2 = (Σ ={a, b}, N={S, A, B}, S, P)
P:
S→aA|bB |ε
A → A a |ε | b B
B → B b |ε
Las gramáticas G1 y G2 son equivalentes. Ambas generan el mismo lenguaje:
L={anbm | n,m≥0}={a0b0, a0b1, a1b0, a1b1,…. .}
2.7.- Clasificación de las gramáticas
Noam Chomsky en 1959 clasificó las gramáticas en cuatro familias (Jerarquía de Chomsky) de
gramáticas, que difieren, atendiendo a la forma que pueden tener sus reglas de producción.
Sea una gramática G = (Σ, N, S, P) P: { α→β,..} α ∈ ( N | Σ)* N ( N | Σ)*, β ∈ ( N | Σ)*
clasificaremos las gramáticas, en los siguientes cuatro tipos:
- Gramáticas de Tipo 3 (Gramáticas regulares).
Pueden ser a su vez, de dos tipos:
Lineales por la derecha. Todas sus producciones son de la forma:
A → aB
A→a
A →ε ¸
en donde A, B ∈ N , a ∈ Σ
Lineales por la izquierda. Todas sus producciones son de la forma:
A → Ba
A→a
A →ε ¸
en donde A, B∈ N , a ∈ Σ
Los lenguajes generados por estas gramáticas se llaman lenguajes regulares o lenguajes de tipo 3 y,
todos forman la clase de lenguajes L3.
* Existen otras definiciones sobre las gramáticas de tipo 3, entre otras, como aquellas que permiten
las producciones de la forma: A→aB, A→a y S→ ε (siempre que el lenguaje tenga la palabra vacía)
En nuestro caso hemos cogido la más simple ya que permite una mayor flexibilidad en el tratamiento
y transformación entre los mecanismos regulares.
- Gramáticas de Tipo 2 (Gramáticas libres del contexto).
Las producciones son de la forma:
A →α
donde A ∈ N , α ∈ ( N | Σ)*
Los lenguajes generados por este tipo de gramáticas se llaman lenguajes libres del contexto o
lenguajes de tipo 2 y, todos forman la clase de lenguajes L2.
- Gramáticas de Tipo 1 (Gramáticas sensibles al contexto).
Las producciones son de la forma:
α →β
en donde α ∈ ( N | Σ)* N ( N | Σ)*, β ∈ ( N | Σ)*
|α|≤|β|
Se permite además la producción S →ε ¸ siempre y cuando no aparezca el símbolo no terminal S en
la parte derecha de ninguna regla de producción.
El sentido de estas reglas de producción δAγ→δβγ es el de especificar que el símbolo A puede ser
reemplazado por β, en una derivación directa sólo cuando A aparezca en el “contexto" de δAγ .
26
Las producciones de este tipo de gramáticas cumplen siempre que la parte izquierda tiene longitud
menor o igual que la parte derecha, pero nunca mayor (excepto para S →ε ). Esto quiere decir que la
gramática es no contráctil.
Los lenguajes generados por las gramáticas de tipo 1 se llaman lenguajes sensibles al contexto y,
forman el conjunto de lenguajes de tipo L1
- Gramáticas de Tipo 0 (Gramáticas con estructura de frase, no restringidas)
Son las gramáticas más generales, que por ello también se llaman gramáticas sin restricciones. Esto
quiere decir que las producciones pueden ser de cualquier tipo permitido, es decir, de la forma:
α→β , α є ( N | Σ)*N ( N | Σ)*
β є ( N | Σ)*
Los lenguajes generados por estas gramáticas son los lenguajes con estructura de frase, que se
agrupan en la clase de lenguajes de tipo de L0.
Estos lenguajes también se conocen como lenguajes recursivamente enumerables.
Relación de inclusión de las gramáticas: Tipo 3⊆ Tipo 2⊆ Tipo 1⊆ Tipo 0
Tipo 0
Tipo 1
Tipo 2
Tipo 3
2.8.- Determinación del tipo de una gramática
Para determinar el tipo de una gramática G, basta por equiparar las producciones de dicha gramática a
las producciones de las familias de los tipos de gramáticas, empezando por las producciones de tipo
superior, de no ser así se baja de tipo hasta encontrar un tipo que coincida con la dada.
Ejemplo, dada la siguiente gramática determinar el tipo de la misma.
G = (Σ ={a,b}, N={S,A,B},S, P)
P:
S → AB
A → Aa |ε
B →Bb |ε
La producción S → AB ∉ tipo 3 la gramática G no es de tipo 3
La producción S → AB ∉ tipo 3 pero si a las gramáticas de tipo 2, por lo que la gramática G es una
gramática de tipo 2.
2.9.- Tipos de lenguajes formales
De los tipos de familias de gramáticas creados por Noam Chomsky (Jerarquía de Chomsky) se
obtiene una clasificación en los lenguajes que generan.
Dado que un lenguaje, puede ser generado por varias gramáticas, gramáticas equivalentes que
pueden ser de tipo diferente, existe un problema a la hora de relacionar lenguajes con gramáticas, que
tipo de gramática asociar al lenguaje.
Para solucionar la anterior indecisión, podemos aplicar el siguiente axioma a la hora de determinar
el tipo del lenguaje generado por la gramática: un lenguaje es de tipo N (N=0,1,2,3), si existe una
gramática con un tipo N que lo genera y no otra de un tipo N superior.
Sea el lenguaje L(Σ)={anbm | n, m≥0}={a0b0, a0b1, a1b0, a1b1,….}, lenguaje que puede ser generado
entre otras por las gramáticas G1 y G2
Ejemplo: G1 = (Σ ={a, b}, N={S, A, B}, S, P)
P: S → AB , A → Aa |ε , B →Bb |ε
G1 es una gramática de tipo 2
27
G2 = (Σ ={a, b}, N={S, A, B}, S, P)
P:
S → aA| bB |ε , A → Aa |ε |bB , B →Bb |ε
G2 es una gramática de tipo 3
Para determinar el tipo al que pertenece el lenguaje, será el tipo de la gramática con el tipo superior
que lo genere, para este caso el tipo del lenguaje L(Σ)={anbm | n, m≥0 } será de Tipo 3 (no existe una
gramática de un tipo superior que lo genere).
Sea el lenguaje L(Σ )={anbn | n≥0}={a0b0, a1b1, a2b2, a3b3,….
G = (Σ ={a,b}, N={S,A,B},S, P)
P: S → aSb |ε
El lenguaje anterior es de tipo 2, no existe una gramática de un tipo superior que lo genere.
El conjunto de los lenguajes de tipo 3 (regulares) definidos sobre un alfabeto Σ, está incluido
propiamente en el conjunto de los lenguajes tipo 2 ( libres de contexto) y, éste a su vez, está incluido
propiamente en el conjunto de los lenguajes de tipo 1 (sensibles al contexto), que lógicamente está
incluido en el conjunto de lenguajes tipo 0 (con estructura de frase).
Relación de inclusión de los tipos de lenguajes: L3⊆ L2 ⊆ L1⊆ L0
L0
L1
L2
L3
Ejercicios de gramáticas
1- Obtener una gramática para cada uno de los siguientes lenguajes:
a) ∑= {a, b, c}, L = {an bm cn | n, m >= 1}
b) ∑= {a, b, c}, L = {an bm cn+2m | n >=0, m >= 1}
c) ∑= {a, b, c}, L = {an bn+m cm | n >= 1, m >= 0}
d) ∑= {a, b}, L = {an bm | n >= m >= 1}
e) ∑= {a, b}, L = {an bm | n > m >= 0}
f) ∑ = {a, b, c}, L = {am bn ck | m > n + k ; n, k >=0}
g) ∑ = {a, b}, L = { anbm | m <= n <= 2m ; n, m >=0}
h) ∑ = {a, b}, L = { anbm | n <>m ; n, m >0}
2- Construir una gramática que especifique el siguiente lenguaje:
L = { w ∈ {0, 1, 2}* | w contiene exactamente dos o tres símbolos 0 en cualquier posición}.
¿Sería posible obtener una gramática regular o de tipo 3?
3- Para las gramáticas que se proponen a continuación, determinar el tipo de las mismas y el lenguaje
generado.
a) S → 0S | A
A → 0B | 0
B → 1A
b) S→ abA | bbB
A → bC
B→a
C → cS | ε
28
4- Sea la siguiente gramática definida sobre el alfabeto ∑ = {a, +, = }
S → BSa | aSa | a+a=aa
Ba → aB
B+ → +a
Obtener el lenguaje generado por la misma
5 - Dado el alfabeto Σ= {a, +, = } y el siguiente lenguaje: L={an+am=an+m | n, m > 0}, construir una
gramática de tipo 2 que genere dicho lenguaje.
6- Dado el alfabeto Σ = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + ,- ,*, / }, sea el lenguaje L formado por todas las
expresiones aritméticas que se pueden formar con los símbolos de este alfabeto, donde 0, 1, 2, 3,
4, 5, 6, 7, 8, 9 son operandos y , + ,- ,*, / son los operadores binarios de suma, resta, producto y
división respectivamente. Por ejemplo, pertenecen a este lenguaje las palabras 1, 1+0 y 5+3*8/7,
y no pertenecen: 11, +1 ó 1*/2.
Determinar el tipo del lenguaje L, justificándolo mediante un mecanismo formal del tipo
correspondiente.
7- Sea el lenguaje de sumas de números naturales sobre Σ = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + }.
Ejemplos de palabras del lenguaje: 2+67+9870 ó 35
Ejemplos de palabras no válidas: 34+ ó 34++23
Representar mediante una gramática de tipo 2 los siguientes lenguajes:
a) Sea L1 el lenguaje de sumas en el que los números naturales no pueden tener ceros a la
izquierda. El número 0 se representará por un único cero, nunca por una secuencia de ceros.
i. Ejemplo:10+0
ii. Ejemplo de palabra no válida: 03+12
b) Sea L2 el lenguaje de sumas en el que los números naturales pueden empezar por 0, aunque
no sea el número 0, pero en cada suma se cumple siempre la condición de que el primer y el
último número tienen siempre la misma longitud (al menos habrá dos números).
i. Ejemplo: 10+1+01
ii. Ejemplos de palabras no válidas: 03+129 , 1
c) Sea L3 el lenguaje de sumas en el que los números naturales pueden empezar por 0, aunque
no sea el número 0, pero en el que se cumple la condición de que al menos dos números
tienen la misma longitud.
i. Ejemplo: 0503+30+450+89+203+03
8 - Dado el alfabeto Σ={a,b,0,1}, sea el lenguaje L={w·(0|1)·v | v,w pertenecen a {a,b}+ y |w|=|v|}, en
el que además se cumplen las siguientes restricciones:
• Si el dígito central es 0, w deberá empezar por a
• Pero si el dígito central es 1, entonces es v quien empezará por a.
Ejemplos de palabras válidas: aba0bba, a0a, a0b, a1a, b1a, abb1abb
Ejemplos de algunas palabras no válidas: b0a, ba0aa, a1b, ba1ba
Obtener una gramática independiente del contexto que genere el lenguaje pedido, teniendo en
cuenta las restricciones.
9- Con el alfabeto ∑={a,b,c} se define el lenguaje L = {w·cn | w pertenece a {a,b}+, n>0} en el que
además se cumple que:
• si w termina en a, entonces la longitud de w es mayor o igual que n
• si w termina en b, entonces la longitud de w es menor o igual que n
Obtener una gramática independiente del contexto que genere el lenguaje pedido, teniendo en
cuenta las restricciones pedidas.
10 - Dado el alfabeto Σ= {a, b} sea el lenguaje: L = { vbbw | v, w ∈ {a,b}* y |v| >= |w| (la
longitud de v es mayor o igual que la de w) }
Obtener una gramática independiente del contexto que genere el lenguaje pedido, teniendo en
cuenta las restricciones pedidas.
29
Descargar