manual completo de programacion de sistemas i

129
MANUAL DE PROGRAMACIÓN DE SISTEMAS I Programación de Sistemas 1 Semestre: Sexto Núm. Horas/semana: 4 Créditos: 8 Objetivos generales: El alumno estará capacitado para diseñar, construir e implementar de la manera más eficiente los analizadores léxicos y sintácticos de un compilador las características y funcionamiento de cargadores, emsambladores y macroprocesadores. Bibliografía Básica: COMPILADORES Principios, técnicas y herramientas Aho, Sethi & Ullman Ed. Addison-Wesley iberoamericana MODERN COMPILER IMPLEMENTATION IN C Basic techniques Appel, Andrew W. Ed. Cambridge FUNDAMENTOS DE COMPILADORES Cómo traducir al lenguaje de computadora Lemone, Karen A. Ed. CECSA COMPILADORES Conceptos fundamentales Teufel, Schmidt & Teufel Ed. Addison-Wesley iberoamericana LIC. MARTHA MARTINEZ MORENO 1

Upload: zjulca

Post on 05-Jul-2015

1.608 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Programación de Sistemas 1

Semestre: SextoNúm. Horas/semana: 4

Créditos: 8

Objetivos generales:

El alumno estará capacitado para diseñar, construir e implementar de la manera más eficiente los analizadores léxicos y sintácticos de un compilador las características y funcionamiento de cargadores, emsambladores y macroprocesadores.

Bibliografía Básica:COMPILADORES Principios, técnicas y herramientasAho, Sethi & UllmanEd. Addison-Wesley iberoamericana

MODERN COMPILER IMPLEMENTATION IN CBasic techniquesAppel, Andrew W.Ed. Cambridge

FUNDAMENTOS DE COMPILADORESCómo traducir al lenguaje de computadoraLemone, Karen A.Ed. CECSACOMPILADORESConceptos fundamentalesTeufel, Schmidt & TeufelEd. Addison-Wesley iberoamericana

PLAN DE TRABAJO

LIC. MARTHA MARTINEZ MORENO1

Page 2: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

1. Datos Generales.

Materia: Teórica/PrácticaHoras de Clase por semana: 4 Hrs.Horas de clase por semestre: 60 Hrs.Carrera en que se imparte: Ing. en Sist. Comp.

Semestre: Sexto

2. Ubicación de la materia.

A. Ubicación teórica. Se imparte en sexto semestre; sus antecedente son

las materias de Lenguajes y Autómatas y Admón. De Archivos.

Materias paralelas: ninguna Materias subsecuentes: Programación de

Sistemas II

B. Ubicación practica. Tipo de Alumnos: diversos; algunos trabajan. Son

de clase baja-media-alta, ambos sexos. Grupo: 20 alumnos por grupo promedio. Horario: matutino Recursos: salón austero en todo o con clima, Sala

Audiovisual, TV, Video, PC, Proy. De acetatos.

3. Objetivos generales de aprendizaje.A. Objetivos informativos.

Aplicará las estructuras básicas de diseño de compiladores (NFA´s, e.r. y C.F.G.´s)

Comprenderá el funcionamiento de traductores, ensambladores, etc.

Conocerá y manejará las herramientas para diseño de sw de base (Java CC).

Diseñará una CFG para la construcción de un Lenguaje de Programación en sus dos primeras fases (léxica y sintáctica)

B. Objetivos formativos. Intelectual. Que el alumno aprenda a

preparar exposiciones profesionales y exponer sus ideas de manera clara y por escrito.

LIC. MARTHA MARTINEZ MORENO2

Page 3: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Intelectual. Que el alumno aprenda a pensar, razonar, analizar, sintetizar, resumir y esquematizar.

Intelectual. Que el alumno realice investigación y a discutir lo investigado con otros.

Humano. Que el alumno tenga un deseo de superación continua, espíritu de profesionalismo, calidad y excelencia.

Social. Que el alumno aprenda a convivir con diferentes grupos, con un espíritu de colaboración y participación.

Profesional. Que el alumno identifique la diferencia en el perfil de un Ingeniero en Sistemas Computacionales en relación con el resto de los usuarios de computadoras.

Profesional. Que el alumno diseñe herramientas (software de base) para la generación de nuevo conocimiento.

4. Metodología de trabajo.

En este curso se planean exposiciones de temas de repaso, reforzando la investigación con dinámicas grupales. Además se forman equipos de trabajos para el desarrollo de proyectos de software de base (equipos no mayores de 3 integrantes) pudiendo ser estas agrupaciones al azar o a gusto de los integrantes. Se utiliza, para realzar la clase equipo audiovisual como TV, PC y Proyector de acetatos. La revisión del proyecto final se realiza en dos partes para mayor facilidad del alumno.

UNIDAD I

LIC. MARTHA MARTINEZ MORENO3

Page 4: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

INTRODUCCIÓN

CONCEPTOS

PROGRAMACIÓN DE SISTEMAS: Conjunto de reglas para crear soluciones a problemas computables. Conjunto de herramientas que nos permiten crear software de base que son de utilidad para interactuar con la máquina.

SOFTWARE DE BASE: Compilador, Querys, Sistema Operativo, Cargador.

AUTÓMATA: Son las cadenas posibles que aceptan un lenguaje.

EXPRESIONES REGULARES: Conjunto de símbolos que aceptan una palabra reservada.

GRAMÁTICA: Reglas para escribir las sentencias del lenguaje.

LENGUAJES DE PROGRAMACIÓN

LENGUAJE DE PROGRAMACIÓN: Es la notación formal para la descripción de algoritmos, basada en un conjunto de instrucciones en alto nivel, que finalmente pasarán a bajo nivel para interactuar con el hardware y generar herramientas de trabajo.

Los lenguajes son sistemas de comunicación. Un lenguaje de programación consiste en todos los símbolos, caracteres y reglas de uso que permiten a las personas "comunicarse" con las computadoras. Existen por lo menos varios cientos de lenguajes y dialectos de programación diferentes. Algunos se crean para una aplicación especial, mientras que otros son herramientas de uso general más flexibles que son apropiadas para muchos tipos de aplicaciones. En todo caso los lenguajes de programación deben tener instrucciones que pertenecen a las categorías ya familiares de entrada/salida, cálculo/manipulación de textos, lógica/comparación y almacenamiento/recuperación.

LIC. MARTHA MARTINEZ MORENO4

Page 5: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

EJEMPLO DE SÍMBOLOS QUE COMPONEN UN PROGRAMA :

No obstante, aunque todos los lenguajes de programación tienen un conjunto de instrucciones que permiten realizar dichas operaciones, existe una marcada diferencia en los símbolos, caracteres y síntaxis de los lenguajes de máquina, lenguajes ensambladores y lenguajes de alto nivel.

Lenguaje de máquina

El lenguaje de máquina de una computadora consta de cadenas de números binarios (ceros y unos) y es el único que "entienden" directamente los procesadores. Todas las instrucciones preparadas en cualquier lenguaje de máquina tienen por lo menos dos partes. La primera es el comando u operación, que dice a la computadora cuál es la función que va a realizar. Todas las computadoras tiene un código de operación para cada una de sus funciones. La segunda parte de la instrucción es el operando, que indica a la computadora donde hallar o

LIC. MARTHA MARTINEZ MORENO5

LENGUAJE

PASCAL

DELIMITADORES

OPERACIONALES

+,-,*,/,MOD

PALABRAS RESERVADAS

:=ASIGNACION

RELACIONALES

==<>><

ORNOTAND

LOGICOS

ARITMETICOS

IDENTIFICADORES

SIMBOLOS QUE RECHAZA EL LENGUAJE

FUNCIONES

SIMBOLOS ESPECUALES

DECLARACION DE CONSTANTESDECLARACION DE VARIABLESCICLOS

LENGUAJE

PASCAL

DELIMITADORES

OPERACIONALES

+,-,*,/,MOD

PALABRAS RESERVADAS

:=ASIGNACION

RELACIONALES

==<>><

ORNOTAND

LOGICOS

ARITMETICOS

IDENTIFICADORES

SIMBOLOS QUE RECHAZA EL LENGUAJE

FUNCIONES

SIMBOLOS ESPECUALES

DECLARACION DE CONSTANTESDECLARACION DE VARIABLESCICLOS

Page 6: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

almacenar los datos y otras instrucciones que se van a manipular; el número de operandos de una instrucción varía en las distintas computadoras.

Lenguaje ensamblador

La comunicación en lenguaje de máquina es particular de cada procesador que se usa, y programar en este lenguaje es muy difícil y tedioso, por lo que se empezó a buscar mejores medios de comunicación con ésta.

A principios de la década de 1950, y con el fin de facilitar la labor de los programadores, se desarrollaron códigos mnemotécnicos para las operaciones y direcciones simbólicas. Uno de los primeros pasos para mejorar el proceso de preparación de programas fue sustituir los códigos de operación numéricos del lenguaje de máquina por símbolos alfabéticos, que conforman un lenguaje mnemotécnico. Todas las computadoras actuales tienen códigos mnemotécnicos aunque, naturalmente, los símbolos que se usan varían en las diferentes marcas y modelos. La computadora sigue utilizando el lenguaje de máquina para procesar los datos, pero los programas ensambladores traducen antes los símbolos de código de operación especificados a sus equivalentes en lenguaje de máquina. Los lenguajes ensambladores tienen ventajas sobre los lenguajes de máquina. Ahorran tiempo y requieren menos atención a detalles . Se incurren en menos errores y los que se cometen son más fáciles de localizar. Además, los programas en lenguaje ensamblador son más fáciles de modificar que los programas en lenguaje de máquina. Pero existen limitaciones. La codificación en lenguaje ensamblador es todavía un proceso lento. Además, una desventaja importante de estos lenguajes es que tienen una orientación a la máquina. Es decir, están diseñados para la marca y modelo específico de procesador que se utiliza.

En el principio de la computación este era el lenguaje que tenía que "hablar" el ser humano con la computadora y consistía en insertar en un tablero miles de conexiones y alambres y encender y apagar interruptores. Aunque en la actualidad ya no se emplea, es importante reconocer que ya no es necesario que nos comuniquemos en este lenguaje de "unos" y

LIC. MARTHA MARTINEZ MORENO6

Page 7: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

"ceros", pero es el que internamente una computadora reconoce o "habla".

Lenguajes de alto nivel

Los primeros programas ensambladores producían sólo una instrucción en lenguaje de máquina por cada instrucción del programa fuente. Para agilizar la codificación, se desarrollaron programas ensambladores que podían producir una cantidad variable de instrucciones en lenguaje de máquina por cada instrucción del programa fuente. Dicho de otra manera, una sola macroinstrucción podía producir varias líneas de código en lenguaje de máquina.

El desarrollo de las técnicas mnemotécnicas y las macroinstrucciones condujo, a su vez, al desarrollo de lenguajes de alto nivel que a menudo están orientados hacia una clase determinada de problemas de proceso.A diferencia de los programas de ensamble, los programas en lenguaje de alto nivel se pueden utilizar con diferentes marcas de computadoras sin tener que hacer modificaciones considerables. Esto permite reducir sustancialmente el costo de la reprogramación cuando se adquiere equipo nuevo.

Los lenguajes de programación se dividen en 4 principales paradigmas: imperativo, funcional, orientado a objetos y lógico.

Lenguajes imperativos

Son llamados así porque están casados en comandos que actualizan variables que están en almacenamiento.

En 1950, cuando los programadores reconocieron que las variables y comandos de asignación constituyen una simple pero útil abstracción de memoria que se actualiza.

Debido a su relación cerrada con las arquitecturas de las máquinas, los lenguajes de programación imperativa pueden ser implementados eficientemente. La razón fundamental de la programación imperativa está relacionado a la naturaleza y propósito de la programación.

LIC. MARTHA MARTINEZ MORENO7

Page 8: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

ADA

C

CLIPPER & XBASE

ENSAMBLADOR

QUICKBASIC

BASIC

EUPHORIA

FORTRAN

PASCAL

Lenguajes funcionales

Los lenguajes funcionales se basan en el concepto de función, por tanto, el objeto básico y fundamental que manejamos son las funciones, que se pueden considerar las principales estructuras de control en este tipo de lenguajes.

La base fundamental para comprender este tipo de lenguajes consiste en ver los programas y aplicaciones expresados a través de funciones y aplicación entre funciones.

Las características fundamentales de estos lenguajes se exponen a continuación:

a. Utilización de funciones sobre elementos de primer orden.b. Utilización de funciones polimórficas, es decir, aquéllas cuyo

tipo devuelto depende del tipo de los argumentos.

c. Utilización de funciones de orden superior, es decir, aquellas funciones que aceptan en su lista de argumentos otras funciones.

LIC. MARTHA MARTINEZ MORENO8

Page 9: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

d. Evaluación perezosa (o lazy), es decir, las expresiones no se evalúan hasta que no se necesitan los resultados. Es justamente, lo contrario de la evaluación eager(ansiosa), donde la evaluación de los argumentos se realiza antes de aplicar la función. La evaluación perezosa nos proporciona la ventaja de trabajar con listas potencialmente infinitas.

e. Existencia de un sistema de tipos fuerte

f. Uso del Patter Matching: comprobación de patrones.

Vistas las características fundamentales de los lenguajes funcionales, estos pueden clasificarse en dos tipos:

Puros: aquellos en los que sólo podemos aplicar funciones. Estas son las únicas estructuras de control. Posee evaluación perezosa.

Impuros: aquellos que poseen evaluación ansiosa.

Ejemplos de lenguajes funcionales:

ML CAML

Haskell

Sheme

LISP

Lamda – Cálculo

Iswim

APL

FP

Hope

Miranda

LIC. MARTHA MARTINEZ MORENO9

Page 10: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Eden

Gofer

Erlang

Programación Orientada a Objetos

SmallTalk

Programación Orientada a la lógica

PROLOG

LENGUAJES ESTRUCTURADOS

Los lenguajes estructurados incorporan una serie de instrucciones que facilitan la construcción modular de los programas así como corrección de errores y soporte de sistemas.

Esto facilita la comprensión del código por cualquier otro programador o analista y hace más fácil la tarea de probar y depurar los programas.

Además poseen una abundante variedad de instrucciones que facilitan el flujo o secuencia de los procedimientos o pasos que debe seguir el programa para la realización de su objetivo.

ALGOL ADA

C

COBOL

PASCAL

LENGUAJES DE INTELIGENCIA ARTIFICIAL

Podemos distinguir tres grandes estilos o subfamilias de los lenguajes de inteligencia artificial. Los tres estilos de programación son

LIC. MARTHA MARTINEZ MORENO10

Page 11: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

los siguientes: programación funcional, programación relacional y programación por objetos. El lenguaje más representativo del estilo funcional es el LISP, LOGO por su identificación con LSIP, cae de lleno dentro de este estilo. El lenguaje más representativo del estilo relacional PROLOG. El lenguaje más representativo del estilo de programación por objetos es el SMALLTALK, pero existen varios dialectos de LISP que permite programar en esta forma.

TRADUCTORES

Los traductores son programas que permiten pasar de un programa fuente a un programa objeto.

En los lenguajes de bajo nivel los programas que permiten pasar de un programa fuente a un programa objeto se llaman programas ensambladores, mientras en los lenguajes de alto nivel estos programas se denominan compiladores e intérpretes.

INTÉRPRETES

Un intérprete es un traductor que toma un programa fuente, lo traduce a un programa objeto instrucción por instrucción, al mismo tiempo que ejecuta el programa.

COMPILADORES

Los Compiladores son programas que traducen los programas fuentes a programas objetos.

El compilador traduce sentencia a sentencia cada una de las instrucciones del programa fuente a código máquina y posteriormente ejecuta el programa.

ESTRUCTURA DE UN COMPILADOR

La estructura de un compilador, esta dividida en cuatro grandes módulos, cada uno independiente del otro, se podría decir que un compilador esta formado por cuatros módulos mas a su vez. El

LIC. MARTHA MARTINEZ MORENO11

Page 12: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

primero de ellos es el preprocesador, es el encargado de transformar el código fuente de entrada original en el código fuente puro. Es decir en expandir las macros, incluir las librerías, realizar un preprocesado racional (capacidad de enriquecer a un lenguaje antiguo con recursos más modernos), extender el lenguaje y todo aquello que en el código de entrada sea representativo de una abreviatura para facilitar la escritura del mismo.

El segundo modulo es el de compilación que recibe el código fuente puro, este es él modulo principal de un compilador, pues si ocurriera algún error en esta etapa el compilador no podría avanzar. En esta etapa se somete al código fuente puro de entrada a un análisis léxico gráfico, a un análisis sintáctico, a un análisis semántico, que construyen la tabla de símbolos, se genera un código intermedio al cual se optimiza para así poder producir un código de salida generalmente en algún lenguaje ensamblador.

El tercer modulo es el llamado modulo de ensamblado, este modulo no es ni más mi menos que otro compilador pues recibe un código fuente de entrada escrito en ensamblador, y produce otro código de salida, llamado código binario no enlazado. Si por un momento viéramos a este modulo como un programa independiente, veríamos que en este caso los términos programa compilador y proceso de compilación son los mismos. Pues este modulo no es mas que un compilador, que en su interior realiza como su antecesor un análisis léxico gráfico, un análisis sintáctico, un análisis semántico, crea una tabla de símbolos, genera un código intermedio lo optimiza y produce un código de salida llamado código binario no enlazado, y a todo este conjunto de tares se los denomina proceso de compilación. Como se puede ver este compilador (llamado ensamblador) a diferencia de los demás compiladores no realiza una expansión del código fuente original(código fuente de entrada), tiene solamente un proceso de compilación y por supuesto no enlaza el código fuente. Es un compilador que carece de los módulos de preprocesado y enlazado, y donde los módulos de compilación y ensamblado son los mismos.

El cuarto y ultimo modulo es el encargado de realizar el enlazado del código de fuente de entrada (código maquina relocalizable) con las librerías que necesita, como así también de proveer al código de las rutinas necesarias para poder ejecutarse y cargarse a la hora de llamarlo para su ejecución, modifica las direcciones relocalizables y ubica los

LIC. MARTHA MARTINEZ MORENO12

Page 13: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

datos en las posiciones apropiadas de la memoria. Este ultimo modulo es el que produce como salida el código binario enlazado. Ya sea dinámico o estático, al decir dinámico se refiere a que el código producido utiliza librerías dinámicas (librerías ya cargadas en el sistema), esto implica que se obtendrá un código más corto y que se actualizara automáticamente si aparece alguna nueva versión de las librerías, mientras que el estático se refiere al echo que no se realiza enlace con ninguna librería y por lo tanto se obtendrá un código mas largo con una copia de las rutinas de librería que necesita.

Estructura del proceso de Compilación:

Analizando en detalle el proceso de compilación, se divide en dos grandes fases, una de Análisis y la otra de Síntesis.

Fase de Análisis:

En el llamado análisis lexicográfico o léxico, el compilador revisa y controla que las "palabras" estén bien escritas y pertenezcan a algún tipo de token (cadena) definido dentro del lenguaje, como por ejemplo que sea algún tipo de palabra reservada, o si es el nombre de una variable que este escrita de acuerdo a las pautas de definición del lenguaje. En esta etapa se crea la tabla de símbolos, la cual contiene las variables y el tipo de dato al que pertenece, las constantes literales, el nombre de funciones y los argumentos que reciben etc.

En el análisis sintáctico como su nombre lo indica se encarga de revisar que los tokens estén ubicados y agrupados de acuerdo a la definición del lenguaje. Dicho de otra manera, que los tokens pertenezcan a frases gramaticales validas, que el compilador utiliza para sintetizar la salida. Por lo general las frases gramaticales son representadas por estructuras jerárquicas, por medio de árboles de análisis sintáctico. En esta etapa se completa la tabla de símbolos con la dimensión de los identificadores y los atributos necesarios etc.

El análisis semántico se encarga de revisar que cada agrupación o conjunto de token tenga sentido, y no sea un absurdo. En esta etapa se reúne la información sobre los tipos para la fase posterior, en esta etapa se utiliza la estructura jerárquica de la etapa anterior y así poder determinar los operadores, y operandos de expresiones y preposiciones.

LIC. MARTHA MARTINEZ MORENO13

Page 14: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Fase de Síntesis:

Etapa de generación de código intermedio, aunque algunos compiladores no la tienen, es bueno saber de su existencia, en esta etapa se lleva el código del programa fuente a un código interno para poder trabajar mas fácilmente sobre él. Esta representación interna debe tener dos propiedades, primero debe ser fácil de representar y segundo debe ser fácil de traducir al código objeto.

En la etapa de optimización de código, se busca obtener el código mas corto y rápido posible, utilizando distintos algoritmos de optimización.

Etapa de generación de código, se lleva el código intermedio final a código maquina o código objeto, que por lo general consiste en un código maquina relocalizable o código ensamblador. Se selecciona las posiciones de memoria para los datos (variables) y se traduce cada una de las instrucciones intermedias a una secuencia de instrucciones de maquina puro.

La tabla de símbolos no es una etapa del proceso de compilación, sino que una tarea, una función que debe realizar el proceso de compilación. En ella se almacenan los identificadores que aparecen en el código fuente puro, como así también los atributos de los mismos, su tipo, su ámbito y en el caso de los procedimientos el número de argumentos el tipo de los mismos etc. En otras palabras una tabla de símbolos es una estructura de datos, que contiene un registro por cada identificador, y sus atributos. La tabla de símbolo es accedida tanto para escritura como parar lectura por todas las etapas. Detector de errores o manejador de errores, al igual que la tabla de símbolos no es una etapa del proceso de compilación, si no que es una función, muy importante, pues al ocurrir un error esta función debe tratar de alguna forma el error para así seguir con el proceso de compilación (la mayoría de errores son detectados en las etapas de análisis léxico, análisis sintáctico, análisis semántico).

Supongamos que un compilador tiene que analizar la siguiente preposición:

Preposición: suma = var1 + var2 + 10;

LIC. MARTHA MARTINEZ MORENO14

Page 15: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Análisis Léxico El analizador léxico lee los caracteres del programa fuente, y verifica que correspondan a una secuencia lógica (identificador, palabra reservada etc.). Esta secuencia de caracteres recibe el nombre componente léxico o lexema. En este caso el analizador léxico verifica si el identificador id1 (nombre interno para "suma") encontrado se halla en la tabla de símbolos, si no esta produce un error porque todavía no fue declarado, si la preposición hubiese sido la declaración del identificador "suma" en lenguajes C, C++ (int suma;) el analizador léxico agregaría un identificador en la tabla de símbolos, y así sucesivamente con todos los componentes léxicos que aparezcan.

id1= id2+ id3 * 10

Análisis Sintáctico

El analizador sintáctico impone una estructura jerárquica a la cadena de componentes léxicos, generada por el analizador léxico, que es representada en forma de un árbol sintáctico. = / \ id1 + / \ id2 + / \ id3 10

Análisis Semántico

El analizador semántico verificara en este caso que cada operador tenga los operandos permitidos.

= / \ id1 + / \ id2 + / \ id3 tipo_ent

LIC. MARTHA MARTINEZ MORENO15

Page 16: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

| 10

Generador de código intermedio Esta etapa se lleva la preposición a una representación intermedia como un programa para una maquina abstracta.

temp1= tipo_ent(10) temp2= id3 * temp1 temp3= id2 + tem2 id1= temp3

Optimización de código El código intermedio obtenido es representado de una forma mas optima y eficiente.

temp1= id3 * 10.0 id1= id2 + temp1

Generador de código Finalmente lleva el código intermedio a un código objeto que en este caso es un código relocalizable o código ensamblador (también llamado código no enlazado).

MOVF id3, R2 MULT #10.0, R2 MOVF id2, R1 ADDF R2, R1 MOVF R1, id1

Este el código objeto obtenido que es enviado al modulo de ensamblado. Para entender todo esto veamos un ejemplo utilizando como lenguaje en este caso al popular lenguaje de programación C creado por Kernighan y Ritchie. El siguiente código esta definido de acuerdo al standard ANSI C.

#include<stdio.h> void main() { char* frase= " Hola Mundo...!!!"; printf("%s", frase );

LIC. MARTHA MARTINEZ MORENO16

Page 17: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

};

). Al pasar por él modulo de preprocesado, el código fuente queda de la siguiente manera.

# 1 "hmundo.c" # 1 "c:/compilador/include/stdio.h" 1 3 # 1 " c:/compilador/include/sys/types.h" 1 3 # 12 " c:/compilador/include/stdio.h" 2 3 typedef void *va_list; typedef long unsigned int size_t; typedef struct { int _cnt; char *_ptr; char *_base; int _bufsiz; int _flag; int _file; char *_name_to_remove; } FILE;El nuevo código contiene el encabezado o prototipo de la/s función/es que se encuentran en el archivo de cabecera stdio.h, y que serán posiblemente utilizadas en el código fuente original. Este código es pasado al modulo de compilación quien luego de analizarlo y verificar si se encuentra correcto, transformara el código fuente puro (expandido) en código ensamblador y lo envía al modulo de ensamblado.

.file "hmundo.c" compiler_compiled.: ___compiled_c: .text LC0: .ascii " Hola Mundo...!!!\0" LC1:Este código será analizado por él modulo de ensamblado, que lo llevara a código binario no enlazado, y lo enviara al modulo de enlazado. El código de salida enviado al modulo de enlazado es el siguiente. L&#0;³Ú(7ô&#0;.text&#0; @&#0;Œ&#0;Ì&#0;&#0; &#0; ............. .data&#0;@&#0;@&#0;@&#0;

LIC. MARTHA MARTINEZ MORENO17

Page 18: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

.bss&#0;@&#0;@&#0;&#128; Hola Mundo...!!!&#0;%s&#0;v&#0; U‰åƒìèÝÿÿÿÇEü&#0;‹EüPh&#0; èÈÿÿÿƒÄ ÉÃv&#0;.file&#0;þÿ&#0; ghmundo.c&#0;&#0;&#0;&#0;.

UNIDAD II

ELEMENTOS DE LA PROGRAMACIÓN DE SISTEMAS

CARGADORES

La programación de sistemas se refiere a la creación de programas cuya finalidad es servir a otros programas.

Entre los programas que se manejan en la programación de sistemas se encuentran: los sistemas operativos, los compiladores, los manejadores de base de datos.

Un cargador es un programa que coloca en la memoria para su ejecución, el programa guardado en algún dispositivo de almacenamiento secundario.

Dependiendo de la manera en que se manejan los procesos de ligas y carga, podemos clasificar a los cargadores en: cargadores iniciales, absolutos, con reubicación y ligadores.

Cargadores Iniciales: son aquellos que indican ala PC la forma de poner en memoria principal, unos datos almacenados en un periférico de memoria externa (cintas, discos, etc.) sirven para cargar con la memoria pequeños programas que inician el funcionamiento de la computadora.

Cargadores Absolutos: como ya se menciono, el programa cargador pone en memoria las instrucciones guardadas en sistemas externos. Si dichas instrucciones se almacenan siempre en el mismo espacio de memoria (cada vez que se ejecute el programa cargador), se dice que es un cargador absoluto.

LIC. MARTHA MARTINEZ MORENO18

Page 19: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Cargadores con publicación: en ocasiones, un mismo programa necesita ejecutarse en diferentes posiciones de memoria. Para esto, la introducción debe realizarse de forma adecuada, es decir, sin emplear referencias absolutas a direcciones de memoria, si no referencias relativas a una dirección especial llamada Dirección de Reubicación.

El calculo de las direcciones de reubicación las hace el mismo cargador a medida que va cargando las instrucciones en el espacio de memoria que le indique al usuario o el sistema operativo de la maquina.

Cargadores ligadores: conocidos también como “Link Editor” por su término en ingles o simplemente “Linker”, ala acción de motor se le llama vulgarmente “Lindar”.

Montar un programa consiste en añadir al programa objeto. Obteniendo en la traducción las rutinas externas a las que hace referencia dicho programa.

El ensamblador debe permitir dichas referencias y las rutinas debe estar a su vez en lenguaje maquina guardadas en algún elemento accesible al montaje.

Generalmente dichas rutinas se encuentran guardadas en ficheros especiales llamados “Librerías”; donde están almacenadas todas las rutinas externas susceptibles a ser empleados por los diferentes programas del usuario.

Allí va el programa ligador cuando esta realizando el montaje de un programa a buscarlas y las adjunta al programa objeto.

En el proceso de carga reubicable (relocalizable), un mismo programa puede ejecutarse en diferentes posiciones de memoria. Para esto, el programa objeto debe utilizar referencias a relativas a una dirección especial llamada dirección de reubicación.

EI cálculo de las direcciones reubicables es realizado por el cargador a medida quo va ubicando las instrucciones en el espacio de memoria que le indique el sistema operativo.

LIC. MARTHA MARTINEZ MORENO19

Page 20: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Cuando se utilizan subrutinas en un programa, el código ejecutable de cada una de ellas debe encontrarse en memoria al tiempo de ejecución. Para esto, antes de cargar un programa, debe ligarse su código objeto con los códigos objeto (guardados en uno o más archivos) de cada una de las subrutinas invocadas por él, obteniendo así un programa ejecutable que contiene tanto el código del módulo invocador como el código de los módulos invocados. En este punto, es posible guardar el resultado del proceso de liga en un archivo que podrá ser utilizado por un cargador, o el mismo programa ligador puede también realizar la tarea de carga. Esto último evita el tener que guardar el código ejecutable en un archivo, con lo que se ahorra espacio en disco. Este ahorro de espacio en disco se paga con el tiempo gastado al tener que ligar todos los módulos cada vez que se necesite ejecutar el programa.

Este enlace se llama estático porque se realiza antes de ejecutar el programa.

Existe otro proceso llamado enlace dinámico, el cual consiste en enlazar en tiempo de ejecución los módulos que contienen a las subrutinas.

ENSAMBLADORES

Que es ensamblador y para que sirve?

Cuando se empezaron a utilizar símbolos nemotécnicos, se escribieron programas para traducir automáticamente los programas escritos en lenguaje ensamblador a lenguaje máquina. A estos programas traductores se les llamo ensambladores.

La entrada para un ensamblador es un programa fuente escrito en lenguaje ensamblador. La salida es un programa objeto, escrito en lenguaje de máquina. El programa objeto incluye también la información necesaria para que el cargador pueda preparar el programa objeto para su ejecución.

Para evitar confusiones, de aquí en adelante llamaremos lenguaje ensamblador al conjunto de nemotécnicos y a las reglas para su manejo. Al programa que traduce un programa objeto a partir de un programa escrito en lenguaje ensamblador lo llamaremos ensamblador

LIC. MARTHA MARTINEZ MORENO20

Page 21: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Motivos para utilizarlo

Rapidez Mayor control de la computadora Independencia del lenguaje La mayoría de las computadoras pueden ensamblarlo

Motivo para no utilizarlo

Dependencia de hardware Mayor tiempo de codificación Comprensión mas profunda de la computadora Errores mas frecuentes en el programa

Tipos de Ensambladores.

Aunque todos los ensambladores realizan básicamente las mismas tareas, podemos clasificarlos de acuerdo a características.

Así podemos clasificarlos en:

Ensambladores Cruzados (Cross-Assembler).

Se denominan así los ensambladores que se utilizan en una computadora que posee un procesador diferente al que tendrán las computadoras donde va a ejecutarse el programa objeto producido.

El empleo de este tipo de traductores permite aprovechar el soporte de medios físicos (discos, impresoras, pantallas, etc.), y de programación que ofrecen las máquinas potentes para desarrollar programas que luego los van a ejecutar sistemas muy especializados en determinados tipos de tareas.

Ensambladores Residentes.

Son aquellos que permanecen en la memoria principal de la computadora y cargan, para su ejecución, al programa objeto producido. Este tipo de ensamblador tiene la ventaja de que se puede comprobar inmediatamente el programa sin necesidad de transportarlo de un lugar a otro, como se hacía en cross-assembler, y sin necesidad de programas simuladores.

LIC. MARTHA MARTINEZ MORENO21

Page 22: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Sin embargo, puede presentar problemas de espacio de memoria, ya que el traductor ocupa espacio que no puede ser utilizado por el programador. Asimismo, también ocupará memoria el programa fuente y el programa objeto. Esto obliga a tener un espacio de memoria relativamente amplio. Es el indicado para desarrollos de pequeños sistemas de control y sencillos automatismo empleando microprocesadores.

La ventaja de estos ensambladores es que permiten ejecutar inmediatamente el programa; la desventaja es que deben mantenerse en la memoria principal tanto el ensamblador como el programa fuente y el programa objeto.

Macroensambladores.

Son ensambladores que permiten el uso de macroinstrucciones (macros). Debido a su potencia, normalmente son programas robustos que no permanecen en memoria una vez generado el programa objeto. Puede variar la complejidad de los mismos, dependiendo de las posibilidades de definición y manipulación de las macroinstrucciones, pero normalmente son programas bastantes complejos, por lo que suelen ser ensambladores residentes.

Microensambladores.

Generalmente, los procesadores utilizados en las computadoras tienen un repertorio fijo de instrucciones, es decir, que el intérprete de las mismas interpretaba de igual forma un determinado código de operación.

El programa que indica al intérprete de instrucciones de la UCP cómo debe actuar se denomina microprograma. El programa que ayuda a realizar este microprograma se llama microensamblador. Existen procesadores que permiten la modificación de sus microprogramas, para lo cual se utilizan microensambladores.

Ensambladores de una fase.

LIC. MARTHA MARTINEZ MORENO22

Page 23: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Estos ensambladores leen una línea del programa fuente y la traducen directamente para producir una instrucción en lenguaje máquina o la ejecuta si se trata de una pseudoinstrucción. También va construyendo la tabla de símbolos a medida que van apareciendo las definiciones de variables, etiquetas, etc.

Debido a su forma de traducción, estos ensambladores obligan a definir los símbolos antes de ser empleados para que, cuando aparezca una referencia a un determinado símbolo en una instrucción, se conozca la dirección de dicho símbolo y se pueda traducir de forma correcta. Estos ensambladores son sencillos, baratos y ocupan poco espacio, pero tiene el inconveniente indicado.

Ensambladores de dos fases.

Los ensambladores de dos fases se denominan así debido a que realizan la traducción en dos etapas. En la primera fase, leen el programa fuente y construyen una tabla de símbolos; de esta manera, en la segunda fase, vuelven a leer el programa fuente y pueden ir traduciendo totalmente, puesto que conocen la totalidad de los símbolos utilizados y las posiciones que se les ha asignado.

Estos ensambladores son los más utilizados en la actualidad.

El proceso de ensamble de uno, dos o más pasos.

Como se vio en la sección anterior, existen ensambladores que realizan su tarea en una o más fases o pasos.

El proceso de ensamble de un paso consiste en leer una línea de programa fuente y traducirla a lenguaje máquina cuando se trata de una instrucción, o se ejecuta si es una seudoinstrucción.

La tabla de símbolos se va construyendo a medida que se avanza en la lectura de las líneas del programa fuente.

Para que el ensamble de un paso funciones, todos los símbolos deben estar definidos antes de emplearse. Esto debido a que, para traducir correctamente cada instrucción, se debe conocer la dirección de cada uno de los símbolos que intervienen en ella. En otras palabras, no pueden quedar referencias pendientes porque ya no habrá otra oportunidad de resolverlas.

LIC. MARTHA MARTINEZ MORENO23

Page 24: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Tampoco podrán hacerse saltos hacia líneas posteriores. No es posible saltar hacia una línea cuya etiqueta todavía no ha sido definida.

Para resolver el problema que presenta el proceso de ensamble de un paso, se utiliza el proceso de ensamble de dos pasos o fases. En la primera fase, se lee el programa fuente y se construye la tabla de símbolos.

En la segunda fase, se vuelve a leer el programa fuente y se traduce totalmente ya que se conoce la totalidad de los símbolos utilizados (incluyendo las etiquetas) y las posiciones de memoria que se les han asignado. Como ya se conocen las direcciones de las etiquetas utilizadas, pueden realizarse saltos hacia adelante.

Literales y Expresiones.

En computación, las literales son mecanismos mediante los cuales se reservan espacios de memoria para guardar valores de cierto tipo. Generalmente, el término literal se asocia un símbolo para representar la dirección del primer byte de espacio asignado. En el espacio asignado se pueden almacenar valores constantes o variables.

Las expresiones son combinaciones de literales y operadores. En lenguaje ensamblador las expresiones involucran valores constantes y operadores. Los resultados se almacenan como constantes ya que los cálculos ocurren durante el ensamble, no durante la ejecución. Los operadores que se utilizan en las expresiones de lenguaje ensamblador no tienen ningún efecto en tiempo de ejecución del programa ensamblado. No debe confundirse el manejo de expresiones en lenguaje ensamblador con el manejo de expresiones en los lenguajes de alto nivel. En los lenguajes de alto nivel, la evaluación de las expresiones se hace en tiempo de ejecución.

Cada traductor dará sus reglas de construcción de expresiones y, muy importante, de cómo las evalúa.

MACROPROCESADORES

USOS DE UN MACROPROCESADOR

LIC. MARTHA MARTINEZ MORENO24

Page 25: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Con el fin de evitar al programador la tediosa repetición de partes idénticas de un programa, los ensambladores y compiladores cuentan con macroprocesadores que permiten definir una abreviatura para representar una parte de un programa y utilizar esa abreviatura cuantas veces sea necesario.

Para utilizar una macro, primero hay que declararla. En la declaración se establece el nombre que se le dará a la macro y el conjunto de instrucciones que representará.

El programador escribirá el nombre de la macro en cada uno de los lugares donde se requiera la aplicación de las instrucciones por ella representadas.

La declaración se realiza una sola vez, pero la utilización o invocación a la macro (macrollamada) puede hacerse cuantas veces sea necesario.

La utilización de macros posibilita la reducción del tamaño del código fuente, aunque el código objeto tiende a ser mayor que cuando se utilizan funciones.

Es tan común el empleo de macroinstrucciones se les considera como una extensión de los lenguajes. De manera similar se considera al procesador de macroinstrucciones o macroprocesador como una extensión del ensamblador o compilador utilizado.

El macroprocesador se encarga, en una primera pasada, de registrar todas las declaraciones de macros y de rastrear el programa fuente para detectar todas las macrollamadas. En cada lugar donde encuentre una macrollamada, el macroprocesador hará la sustitución por las instrucciones correspondientes. A este proceso de sustitución se le denomina expansión de la macro.

El macroprocesador elabora dos tablas para el manejo de las macros:

Una tabla de macronombres que consiste de los nombres de las macros y un índice que le permite localizar la definición de la macro en otra tabla llamada tabla de macrodefiniciones.

LIC. MARTHA MARTINEZ MORENO25

Page 26: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Como su nombre lo indica, la tabla de macrodefiniciones contiene las definiciones de todas las macros a utilizar en el programa.

En ocasiones es conveniente agrupar macros, de acuerdo a las tareas que realizan, y almacenarlas en archivos que se constituyen en bibliotecas de macros.

De esta manera, cuando se requiera la utilización de alguna macro en particular, se incluye en el programa fuente el archivo de la biblioteca de macros correspondiente.

La mayoría de los ensambladores y compiladores permiten el uso de pseudoinstrucciones condicionales del tipo IF ... ELSE, por medio de las cuales se puede controlar la traducción de ciertos bloques de programa.

SISTEMAS OPERATIVOS

Es un programa que actúa como intermedio entre el usuario y el hardware de una PC y su propósito es proporcionar un entorno en el cual el usuario pueda ejecutar sus programas.

El objetivo principal de un sistema operativo es, entonces, lograr que el sistema de computación se use de manera cómoda, y el objetivo secundario es que el hardware del computador se emplee de manera eficiente.

Un sistema Operativo es parte importante de casi cualquier sistema de cómputo, el cual cuenta con y componentes básicos: hardware, Sistemas Operativos, programas de aplicación y los usuarios.

El principal objetivo de un sistema operativo es proporcionar una interfaz entre el equipo y los programas, para esto debe administrar los recursos del sistema, de tal manera que su uso sea lo mas sencillo y eficiente.

El hardware (unidad central de proceso (UCP), memoria y dispositivos de entrada y salida (E/S)) proporciona los recursos de computación básica básicos.

LIC. MARTHA MARTINEZ MORENO26

Page 27: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Los programas de aplicación (compiladores, sistemas de bases de datos, juegos de vídeo y programas para negocios) definen la forma en que estos recursos se emplean para resolver los problemas de computación de los usuarios. Puede haber distintos usuarios (personas, máquinas, otros computadores) que intentan resolver problemas diferentes; por consiguiente, es posible que haya diferentes programas de aplicación. El sistema operativo controla y coordina el uso del hardware entre los diversos programas de aplicación de los distintos usuarios.

Los sistemas operativos son ante todo administradores de recursos; el principal recurso que administran es el hardware del computador: los procesadores, los medios de almacenamiento, los dispositivos de entrada/salida, los dispositivos de comunicación y los datos. Los sistemas operativos realizan muchas funciones, como proporcionar la interfaz con el usuario, permitir que los usuarios compartan entre sí el hardware y los datos, evitar que los usuarios interfieran recíprocamente, planificar la distribución de los recursos, facilitar la entrada y salida, recuperarse de los errores, contabilizar el uso de los recursos, facilitar las operaciones en paralelo, organizar los datos para lograr un acceso rápido y seguro, y manejar las comunicaciones en red.

No existe una definición universal aceptada de qué forma parte de un sistema operativo y qué no. Una perspectiva sencilla considera como tal todo lo que envía un vendedor cuando se ordena la adquisición del "sistema operativo". Sin embargo, los requisitos de memoria y las características incluidas varían en forma considerable de un sistema a otro. Algunos requieren menos de un megabyte de espacio e incluso carecen de un editor de pantalla, mientras que otros necesitan cientos de megabytes de espacio e incluyen revisores ortográficos y sistemas de ventanas. Una definición más común es que el sistema operativo es el programa que se ejecuta todo el tiempo en el computador (conocido usualmente como núcleo), siendo programas de aplicación todos los demás. La segunda definición es la más común y es la que utilizamos en general.

Quizá sea más fácil definir los sistemas operativos por lo que hacen, en vez de hacerlo por lo que son. El objetivo principal de un sistema operativo es la comodidad para el usuario. Un objetivo secundario es la utilización eficiente del sistema de computación. Este

LIC. MARTHA MARTINEZ MORENO27

Page 28: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

propósito tiene una importancia especial en los grandes sistemas multiusuario compartidos; éstos suelen ser muy costosos, por lo que es deseable hacerlos lo más eficientes posible.

Tipos de Sistemas Operativos.

Una forma de clasificación de los sistemas operativos se basa en el número de usuarios que el sistema puede atender al mismo tiempo.

Así, tenemos sistemas de una sola tarea o trabajo, los cuales pueden atender solo un trabajo (y por lo tanto un solo usuario) a la vez.

Un sistema de multiprogramación permite que se ejecuten varios trabajos al mismo tiempo. El sistema operativo se encarga de intercambiar la atención del procesador entre los programas en ejecución y las funciones de apoyo propias.

Un sistema de multiprocesamiento difiere del sistema de multiprogramación por el hecho de trabajar con varios procesadores a la vez.

Otra forma de clasificar a los sistemas operativos toma en cuente el tipo de acceso que proporcionan al usuario. En este caso, se tienen: sistemas de procesamiento por lotes (batch processing), donde un trabajo se define como una secuencia de instrucciones de control almacenadas en algún dispositivo para que el sistema operativo pueda leer y ejecutar una serie de trabajos, limitando la intervención del operador solo al montaje de cintas y discos. Por otra parte se tienen los sistemas de tiempo compartido (multiusuario) que proporcionan acceso interactivo o conversacional a varios usuarios, proporcionando a cada uno de ellos pequeños intervalos de tiempo de acuerdo a la cantidad de usuarios presentes y a la prioridad de cada uno de los procesos.

COMPILADORES

La necesidad de establecer comunicación con dispositivos de cómputo para un creciente número de usuarios ha obligado a construir herramientas que permitan que esta comunicación se realice de manera más efectiva y con menor consumo de tiempo.

LIC. MARTHA MARTINEZ MORENO28

Page 29: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Esto lo podemos apreciar desde finales de la década de los 50's, cuando con el advenimiento de computadoras comerciales surge también la necesidad de programarlas.

Se diseñaron lenguajes como FORTRAN y COBOL que permiten realizar esta tarea de comunicación al establecer una relación entre los problemas de los usuarios y lo que las máquinas eran capaces de realizar.

Estos primeros lenguajes también vinieron acompañados de un nuevo término: Compilador.

Se le atribuye a Grace Murray Hopper la acuñación de este término y se refería al trabajo que estaba detrás de la programación en aquellos tiempos: existía una biblioteca de programas constituida de un conjunto de rutinas, cada una de ellas probada individualmente; cuando se necesitaba un programa, se elegían las rutinas necesarias de esa biblioteca y se integraban para conformar el proceso que ejecutaría la computadora. Quién realizaba este trabajo de acopio de rutinas y de integración se le denominaba compilador, de ahí que los nuevos lenguajes tuviesen sus propios "compiladores" para la integración del proceso que programar representaba.

En nuestros días, el término aún se conserva aunque con un sentido ligeramente diferente al planteado por Hopper. Hoy en día, un compilador es un traductor que facilita la comunicación entre el programador y la máquina, por medio de un proceso de transformación.

Un compilador es un programa que lee las líneas escritas en un lenguaje de programación (como Pascal) y las traduce a otro que pueda ejecutar la computadora. Los programas compilados se ejecutan más rápido que los interpretados, debido a que han sido completamente traducidos a lenguaje de máquina y no necesitan compartir memoria con el intérprete.

A grandes rasgos un compilador es un programa que lee un programa escrito es un lenguaje, el lenguaje fuente, y lo traduce a un programa equivalente en otro lenguaje, el lenguaje objeto. Como parte importante de este proceso de traducción, el compilador informa a su usuario de la presencia de errores en el programa fuente.

LIC. MARTHA MARTINEZ MORENO29

Page 30: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Clasificación de Compiladores

El programa compilador traduce las instrucciones en un lenguaje de alto nivel a instrucciones que la computadora puede interpretar y ejecutar. Para cada lenguaje de programación se requiere un compilador separado.

El compilador traduce todo el programa antes de ejecutarlo. Los compiladores son, pues, programas de traducción insertados en la memoria por el sistema operativo para convertir programas de cómputo en pulsaciones electrónicas ejecutables (lenguaje de máquina). Los compiladores pueden ser de:

una sola pasada: examina el código fuente una vez, generando el código o programa objeto.

pasadas múltiples: requieren pasos intermedios para producir un código en otro lenguaje, y una pasada final para producir y optimizar el código producido durante los pasos anteriores.

Optimación: lee un código fuente, lo analiza y descubre errores potenciales sin ejecutar el programa.

Compiladores incrementales: generan un código objeto instrucción por instrucción (en vez de hacerlo para todo el programa) cuando el usuario teclea cada orden individual. El otro tipo de compiladores requiere que todos los enunciados o instrucciones se compilen conjuntamente.

Ensamblador: el lenguaje fuente es lenguaje ensamblador y posee una estructura sencilla.

Compilador cruzado: se genera código en lenguaje objeto para una máquina diferente de la que se está utilizando para compilar. Es perfectamente normal construir un compilador de Pascal que genere código para MS-DOS y que el compilador funcione en Linux y se haya escrito en C++.

LIC. MARTHA MARTINEZ MORENO30

Page 31: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Compilador con montador: compilador que compila distintos módulos de forma independiente y después es capaz de enlazarlos.

Autocompilador: compilador que está escrito en el mismo lenguaje que va a compilar. Evidentemente, no se puede ejecutar la primera vez. Sirve para hacer ampliaciones al lenguaje, mejorar el código generado, etc.

Metacompilador: es sinónimo de compilador de compiladores y 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. El desarrollo de los metacompiladores se encuentra con la dificultad de unir la generación de código con la parte de análisis. Lo que sí se han desarrollado son generadores de analizadores léxicos y sintácticos. desarrollados para UNIX. Los inconvenientes que tienen son que los analizadores que generan no son muy eficientes.

Descompilador: es un programa que acepta como entrada código máquina y lo traduce a un lenguaje de alto nivel, realizando el proceso inverso a la compilación.

LIC. MARTHA MARTINEZ MORENO31

Page 32: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

UNIDAD III

ANÁLISIS LÉXICO

EL ANALIZADOR LÉXICO

El analizador léxico lee los caracteres del programa fuente, y verifica que correspondan a una secuencia lógica (identificador, palabra reservada etc.). Esta secuencia de caracteres recibe el nombre componente léxico o lexema.

FUNCIÓN DEL ANALIZADOR LÉXICO

Su principal función consiste en leer los caracteres de entrada y elabora como salida una secuencia de componentes léxicos (tokens) que utiliza el analizador sintáctico para hacer el análisis.

Es la parte del compilador que lee el texto fuente. Recibida la orden "obtén el siguiente componente léxico" del

analizador sintáctico, el analizador léxico lee los caracteres de entrada hasta que pueda identificar el siguiente componente léxico.

Elimina del programa fuente comentarios, espacios en blanco, caracteres TAB y de línea nueva.

Otra función es relacionar los mensajes de error del compilador con el programa fuente.

En algunos compiladores, el analizador léxico se encarga de hacer una copia del programa fuente en el que están marcados los mensajes de error.

LIC. MARTHA MARTINEZ MORENO32

Page 33: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

El analizador Léxico dentro del modelo de un compilador.

En algunas ocasiones, los analizadores léxicos se dividen en dos fases; la primera llamada "examen", y la segunda, "análisis léxico". El examinador se encarga de realizar tareas sencillas, mientras que el analizador léxico es el que realiza las operaciones más complejas.

El objetivo del análisis léxico es gestionar el "bajo nivel" de la entrada (en este caso, texto) y suministrarle al analizador sintáctico la misma ya "filtrada", es decir, conteniendo solamente terminales de la gramática a definir.

En ese trabajo se engloba facilitar la definición de la gramática ya que, el hecho de tener que incluir todas las reglas hasta llegar a los terminales más bajos (caracteres) aumenta indeseablemente la gramática.

Este tipo de trabajos más sencillos (como reconocer números, buscar palabras clave, obviar espacios y demás separadores...) se suelen dejar al analizador léxico, que será un programa muy sencillo y funcionará siempre en tiempo lineal (si le asignamos un trabajo mayor es que seguramente lo estaremos sobrecargando con funciones de análisis sintáctico, lo cual nunca debiera ocurrir).

Realmente, el hecho de esta separación simplemente busca simplificar al máximo la gramática independiente del contexto trabajando con las unidades más abstractas posibles.

La gramática que queda para el análisis léxico, siempre debe ser regular, ya que cada unidad léxica puede venir determinada, por una expresión regular.

LIC. MARTHA MARTINEZ MORENO33

Page 34: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

La relación entre el analizador léxico y el sintáctico son los tokens y la tabla de símbolos, ya que la función principal del análisis léxico es leer los caracteres de entrada y genera como salida una secuencia de componentes léxicos previamente definidos en expresiones regulares.

ASPECTOS A CONSIDERAR PARA EL ANÁLISIS LÉXICO

Un diseño sencillo de un analizador léxico corresponde a unas reglas del lenguaje meramente sencillas, sin embargo estas son algunas consideraciones importantes:

Mientras menos complicadas sean las gramáticas, más fácil será realizar un compilador, sólo que previamente se deben considerar las expresiones regulares.

Hay que considerar si se debe o puede mejorar la eficiencia del compilador.

También mejorar la transportabilidad del compilador, ya que las letras del alfabeto y los errores propios de los dispositivos pueden limitar al analizador léxico.

El trabajo básico es reconocer los terminales (que podemos llamar ya unidades léxicas), y para ello podemos hacer una tabla con todas las posibilidades que encontraremos en el fichero de la entrada y agruparlos en los tipos necesarios.

Caracteres que corresponden directamente con la unidad léxica. Unidades léxicas (terminales) que pueden contener varios

caracteres. Caracteres que pueden figurar en el texto pero que no se

consideran (se desechan). Cualquier otro carácter es erróneo, y producirá, por ello, un error

léxico.

Bajo esta fase se generan los tokens. La generación de los tokens se ha llevado a cabo mediante la carga del fichero que contiene el código fuente en una lista, tras esto se procede a convertir tabuladores y saltos de línea en espacios para después dejar estrictamente los necesarios para diferenciar las distintas palabras introducidas en el archivo fuente.

Por ejemplo, una entrada del tipo:

Si a>b Entonces

LIC. MARTHA MARTINEZ MORENO34

Page 35: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

  a=c+d:

 FinEntonces:

 pasaría a ser Si a>b Entonces a=c+d: FinEntonces: en nuestra lista de entrada.

Una vez que tenemos la lista de entrada limpia se procede a la creación de los tokens; para ello, se ha definido otro tipo de lista llamado Tokens que contendrá el nombre del token (cadena leída por la pantalla) junto con el tipo al que pertenece. 

La implementación del analizador léxico responde al método general de programación visto en teoría, por lo tanto se manejan dos punteros, uno al último carácter leído y otro al último carácter aceptado con lo cual podemos movernos por la lista de entrada en búsqueda del siguiente token sin perder la posición desde donde comenzamos la presente búsqueda. Los tokens, o símbolos terminales de la gramática, han sido representados mediante dos tablas, una para los que van acompañados de un paréntesis y otra para los que no.

El motivo de esta separación ha sido el trato distinto que se les ha dado a ambos para no tener que distinguir, dentro de las órdenes que lleva un parámetro, si entre el final de la orden y el paréntesis que rodea al parámetro hay o no espacios. Así las ordenes Mira (id) y Mira(id) son tratadas como el mismo token y toman el ``('' como parte de él.

Los símbolos separadores (espacios, ``:'', etc.) han sido recogidos del mismo modo en otra tabla dado el trato distinto de los anteriores que recibirán.

Con esta forma de recoger los tokens que reconoce este analizador hemos conseguido no tener que realizar grandes modificaciones sobre esta fase del compilador, si algún día fuera modificado el conjunto de símbolos terminales de la gramática.

ANÁLISIS LÉXICO: DESARROLLO

Objetivo

LIC. MARTHA MARTINEZ MORENO35

Page 36: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

El objetivo básico del scanner o analizador léxico es separar el programa fuente en unidades mínimas y asociarles una determinada clase o token.

¿ Por qué?

Formalmente, para separar lo lineal de lo jerárquico. En el scanner no hay que mirar hacia atrás: las unidades léxicas se forman con caracteres siempre consecutivos y no pueden solaparse dos unidades léxicas. Hay que tener en cuenta que las reglas léxicas son bastante sencillas y no necesitan una notación tan poderosa como una gramática; las expresiones regulares son más sencillas y fáciles de entender que una gramática.

Eficiencia, porque es el único proceso que lee el programa fuente y por ello pueden estudiarse técnicas especializadas de manejo de dispositivos de almacenamiento (la eficiencia es más fácilmente alcanzable partiendo de expresiones regulares que de gramáticas).

Diseño modular del compilador, relacionado con esto último. Portabilidad, ya que la mayoría de diferencias del mismo lenguaje

entre distintos entornos suele ser en caracteres especiales, capitalización, códigos de caracteres, etc. y así se encapsulan en el scanner.

Funciones del scanner

Las funciones básicas del scanner son:·         Leer el programa fuente·         Delimitar los lexemas·         Identificar cada token·         Asignar atributo a cada unidad léxica

Para hacer la interacción con el parser, lo más habitual es convertir al scanner en una subrutina del parser, para que así pueda responder a la orden "obtener la siguiente unidad léxica" recibida del parser. 

Como el scanner lee el programa fuente, también realiza ciertas funciones secundarias como son eliminar comentarios y caracteres no significativos (espacios, tabuladores, retornos de carro...) o preprocesar las macros. 

LIC. MARTHA MARTINEZ MORENO36

Page 37: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Otra función secundaria del analizador léxico es relacionar los mensajes de error con el programa fuente (ya que para que al usuario le sean significativos es necesario que no se pierda esta relación).

En este sentido, en algunos compiladores el scanner va produciendo una copia del fuente en la que se marcan los mensajes de error, se expanden las macros, etc. 

La última función del scanner es detectar los errores léxicos que puedan producirse. A este nivel, los errores se restringen a localizar caracteres no reconocidos, combinaciones de caracteres imposibles, o intentos de lectura por detrás del fin de fichero.

Tipos de unidades léxicas: concepto de patrón

Hay dos clases de unidades léxicas:

Las definidas por el lenguaje: palabras clave, operadores, símbolos especiales. Número finito (quizás 1) de lexemas.

Las definidas por el programador (bajo ciertas normas genéricas): identificadores, literales, comentarios. Normalmente, el número "posible" de lexemas es infinito.

El patrón de las primeras es la simple enumeración de los lexemas; de las segundas, una serie de normas que describen todas las cadenas que corresponden al mismo token.

El patrón permite identificar el token y asignar el atributo (suponiendo delimitado el lexema). Es decir, una vez delimitado el lexema, cumplirá algún patrón, y ese es el que marca el token. Se dice que el patrón concuerda con el lexema que cumple ese patrón.

De este modo, lo primero que debe definirse en todo lenguaje de programación es una tabla con todos los patrones léxicos del lenguaje indicando token y atributo para cada patrón.

Por ejemplo, tomando un subconjunto de Pascal:

Token Lexemas ejemplo

Descripción del patrón

pc-then then (t/T)(h/H)(e/E)(n/N)

LIC. MARTHA MARTINEZ MORENO37

Page 38: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

pc-while while while

pc-write write write

pc-incdec to to

downto downto

op-rel < <

> >

par-ab ( (

par-cer ) )

ident Hola, A77aH, pepe

Letra seguida de letras y dígitos

lit-ent 34, 23442, 1 Dígito no cero seguido de dígitos

lit-real 34.0, 23.4e-5 Dígito no cero seguido de dígitos seguido opcionalmente de punto

decimal y dígitos, seguido opcionalmente de 'E' o 'e' y dígitos

lit-string 'Hola', 'pe pe ' Caracteres entre ', excepto '

En la mayoría de los lenguajes se consideran tokens las construcciones: palabras clave, operadores, identificadores, literales y signos de puntuación. Por supuesto, dependiendo del lenguaje se subdividirán en varios componentes léxicos más.

Normas léxicasEl problema ahora es ¿cómo delimitamos el lexema? En un

principio, podríamos pensar en utilizar un carácter fijo para delimitar lexemas, por ejemplo el espacio. De este modo, la sentencia

if A < 50 then write ( 4 )

puede verse fácilmente separada en los lexemas "if", "A", "<", "50", "then", "write", "(", "4", ")", simplemente observando las series de caracteres separadas por espacios. Dado cada uno de estos lexemas, será muy sencillo ver si cumple algún patrón y devolver el token y atributo correspondiente al patrón que se cumple.

LIC. MARTHA MARTINEZ MORENO38

Page 39: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

En este funcionamiento es fundamental, por ello, la influencia de las distintas normas léxicas: si las palabras clave pueden usarse como identificadores, cuáles son los separadores, etc. De hecho, la evolución de los distintos lenguajes de programación ha ido tendiendo a utilizar reglas léxicas que hagan cada vez más sencillo el desarrollo del scanner.

Vamos a enunciar brevemente las más influyentes en este sentido:

En general, los espacios (y también tabuladores y retornos) no son significativos (es decir, no forman parte de ningún lexema ni constituyen un token) pero sí son separadores.

Las palabras clave suelen ser reservadas. Cuando no lo son (por ejemplo, en PL/I), el scanner debe distinguir entre palabras clave e identificadores, lo cual suele ser complicado.

Por ejemplo:

if then then then = else ; else else = then;

El scanner, no puede diferenciar entre identificadores y palabras clave, o bien diferenciarlos le supone trabajo "extra".

En caso de conflicto, se elige el lexema más largo. Por ejemplo, ifa es el identificador ifa y no la palabra clave if seguida del identificador a. Esto es lógico porque si no limitaríamos mucho los identificadores, al no poder indicar ninguna variable ni nombre de procedimiento o función empezada por do, ni por if, ni por or, ni por and ... (imaginemos lo que ocurriría con lenguajes con muchas palabras reservadas).

 El problema del análisis léxico, se reduce entonces a reconocer el

cumplimiento de patrones por determinados lexemas. Hay dos herramientas formales que nos permiten hacer este trabajo de un modo muy sencillo:

Los autómatas finitos.

LIC. MARTHA MARTINEZ MORENO39

Page 40: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Son las posibles cadenas que acepta un lenguaje. Se compila una expresión regular en un reconocedor construyendo un diagrama de transición que es una instrumentación de un modelo formal denominado autómatas finitos, conocidos también como máquinas de estado finito o (con menos frecuencia en la actualidad) máquinas secuenciales.

Es la representación gráfica de las posibles cadenas que acepta un lenguaje dentro de un conjunto de símbolos, se conforma por una quíntupla donde:

Q es el conjunto de posibles estados representa al alfabeto representa la función de transición y los estados por los que se

acepta cada uno de los símbolosqo estado inicialF conjunto de estados finales

Las expresiones regulares.

Son los posibles símbolos que puede aceptar un lenguaje los cuales se pueden representar con un autómata. Conjunto de símbolos para aceptar una palabra reservada.

Una expresión regular es una fórmula para denotar "ciertos" lenguajes. Adviértase que decimos lenguaje, no cadena de caracteres. Una expresión regular única denota un conjunto de cadenas, es decir un lenguaje, no una simple cadena. Las expresiones regulares se pueden usar para especificar unidades léxicas presentes en un lenguaje de programación. No todos los lenguajes pueden ser expresados utilizando una expresión regular.

Sea un alfabeto å. La expresión regular sobre å y los conjuntos que denotan se definen de manera recursiva como sigue:

LIC. MARTHA MARTINEZ MORENO40

Page 41: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

1. e es una expresión regular y denota al conjunto vacío {e}. 2. Para cada a Î å, a es una expresión regular y denota al

conjunto {a}. 3. Si r y s son expresiones regulares que denotan a los lenguajes

R y S.; respectivamente, entonces tenemos lo siguiente.

    r+s es una expresión regular que denota a los conjuntos R È S.   (r) es una expresión regular que denota al conjunto R.     rs es una expresión regular que denota a los conjuntos RS.     r* es una expresión regular que denota al conjunto R*, equivale desde

cero a más repeticiones del lenguaje R. r+ es una expresión regular que denota al conjunto R+, equivale a una

o más repeticiones del lenguaje R.    ri  es una expresión regular que denota al conjunto Ri, a sí mismo.

a)

TABLAS DE SÍMBOLOS

Es una estructura de datos que contiene un registro por cada token o identificador que define los atributos de ellos mismos. La estructura de datos permite encontrar rápidamente el registro para ser almacenado o consultado por otras fases.

Cuando el análisis léxico detecta un token en el programa fuente, este se introduce e la tabla de símbolos, sin embargo, sus atributos no pueden determinarse durante el análisis léxico.

Por ejemplo, dada la siguiente instrucción:Var

inicial, velocidad: integer ;

Token ValorPosición

en memoria

Utilizado por

; Delimitador

LIC. MARTHA MARTINEZ MORENO41

Page 42: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

integer P. R. Int: S. Punt.

velocidad Id, Separador

inicial IdVar P. R. Var

El tipo entero no se conoce cuando el analizador léxico pasa por toda la instrucción, las fases restantes introducen información sobre los tokens en la tabla y después hace uso de ella.

Cuando se hace el análisis semántico y la generación de código intermedio se necesita saber los tipos de cada uno de los identificadores para comprobar si el programa fuente los usa en forma válida y así generar las operaciones apropiadas con ellos.

MÉTODOS GENERALES PARA LA IMPLANTACIÓN DE UN ANALIZADOR LÉXICO

a) Utilizar un generador de analizador léxico automático. Por ejemplo: La herramienta Lex, que proporciona rutinas para leer la entrada y generar componentes léxicos.

b) Describir el analizador léxico en un lenguaje convencional de programación utilizando subrutinas de entrada / salida.

c) Escribir el analizador léxico en lenguaje ensamblador.

LIC. MARTHA MARTINEZ MORENO42

Page 43: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

UNIDAD IV

ANÁLISIS SINTÁCTICO

El objetivo fundamental del análisis sintáctico es "construir un árbol sintáctico" con la cadena de entrada (el "programa"), en la forma de unidades léxicas que determina el analizador léxico, como se muestra en la siguiente figura: 

El Analizador Sintáctico dentro del modelo de un compilador.

Si esto se consigue, el programa es (sintácticamente) correcto; y es incorrecto en caso contrario. Se supone que el analizador sintáctico informará de cualquier error de sintaxis de manera inteligible. También deberá recuperarse de los errores que ocurren frecuentemente para poder continuar procesando el resto de su entrada.

Existen tres tipos generales de analizadores sintácticos para gramáticas. Los métodos universales de análisis sintáctico, como el algoritmo de Cocke-Younger-Kasami y el de Earley, pueden analizar cualquier gramática. Estos métodos sin embargo, son demasiado ineficientes para usarlos en la producción de compiladores. Los métodos empleados generalmente en la producción de compiladores se clasifican como descendentes o ascendentes.

LIC. MARTHA MARTINEZ MORENO43

Page 44: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Los dos tipos principales de análisis sintáctico son:            Descendente: construye el árbol desde la raíz hacia las hojas.            Ascendente: construye el árbol desde las hojas hacia la raíz.

En ambos casos, se examina la entrada al analizador sintáctico de izquierda a derecha, un símbolo a la vez. Los métodos descendentes y ascendentes más eficientes trabajan sólo con subclases de gramáticas.

La salida del analizador sintáctico es una representación del árbol de análisis sintáctico para la cadena de componentes léxicos producida por el analizador léxico. En la práctica, hay varias tareas que se pueden realizar durante el análisis sintáctico, como recoger información sobre distintos componentes léxicos en la tabla de símbolos, realizar la verificación de tipo y otras clases de análisis semántico. Para la implementación del analizador sintáctico debe de ser necesaria la redefinición de la gramática dada en la práctica de modo que sea ``entendible'' por la computadora. Dentro de la gramática generada se deberá eliminar la recursividad a izquierdas.

Se sabe que los programas pueden contener errores de muy diversos tipos. Por ejemplo los errores pueden ser:

Léxicos, como escribir mal un identificador, palabra clave u operador.

Sintácticos, como una expresión aritmética con paréntesis no equilibrados.

Semánticos, como un operador aplicado a un operando incompatible.

Lógicos, como una llamada infinitamente recursiva.

A menudo, gran parte de la detección y recuperación de errores en un compilador se centra en la fase de análisis sintáctico. Una razón es que muchos errores son de naturaleza sintáctica o se manifiestan cuando la cadena de componentes léxicos que proviene del analizador léxico desobedece la reglas gramaticales que definen el lenguaje de programación. Otra razón es la precisión de los métodos modernos de análisis sintáctico, que pueden detectar la presencia de errores dentro de los programas de una forma muy eficiente. La detección exacta de la presencia de errores semánticos y lógicos en el momento de la compilación es mucho más difícil.

LIC. MARTHA MARTINEZ MORENO44

Page 45: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

El manejador de errores en un analizador sintáctico tiene objetivos fáciles de establecer:

Debe de informar de la presencia de errores con claridad y exactitud.

Se debe de recuperar de cada error con la suficiente rapidez como para detectar errores posteriores.

No de be retrazar de manera significativa el procesamiento de programas correctos.

Afortunadamente lo errores más comunes son simples y a menudo basta con un mecanismo sencillo de manejo de errores. sin embargo, en algunos casos un error pudo haber ocurrido mucho antes de la posición en que se detectó su presencia, y puede ser muy difícil deducir su naturaleza precisa de error. En los casos difíciles, el manejador de errores quizá tenga que adivinar qué tenía en mente el programador cuando escribió el programa.

Varios métodos de análisis sintáctico, detectan un error lo antes posible. Es decir, tienen la propiedad del prefijo viable, la cual quiere decir que detectan la presencia de un error nada más con ver un prefijo de la entrada que no es prefijo de ninguna cadena del lenguaje.

¿Cómo debe informar un manejador de errores de la presencia de un error ?, al menos debe informar del lugar en el programa fuente donde se detecta el error, porque es muy probable que el error real se haya producido en alguno de los componentes léxicos anteriores. Una estrategia común empleada por muchos compiladores e imprimir la línea errónea con un apuntador a la posición donde se detecta el error. Si hay una posibilidad razonable de saber cuál es realmente el error, también se incluye un mensaje de diagnóstico informativo y comprensible; por ejemplo "Falta punto y coma en esta posición".

Una vez detectado el error, ¿ Cómo se debe de recuperar el analizador sintáctico ?, En la mayoría de los casos, no es adecuado que el analizador sintáctico abandone después de detectar el primer error, porque el posterior procesamiento de la entrada podría revelar más errores. Normalmente, hay alguna forma de recuperación del error donde el analizador sintáctico intenta volver él mismo a un estado en el que el procesamiento de la entrada pueda continuar con una esperanza

LIC. MARTHA MARTINEZ MORENO45

Page 46: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

razonable de que hará el análisis de la entrada correcta o de que será manejada correctamente por el compilador.

Una estrategia conservadora para un compilador es inhibir los mensajes de error que provengan de errores descubiertos demasiado cerca uno de otros en la cadena de entrada. Después de descubrir un error sintáctico, el compilador podría exigir que varios componentes léxicos se analizarán sintácticamente con éxito antes de permitir otro mensaje de error. En algunos casos, puede haber demasiados errores como para que el compilador continúe un procesamiento razonable. Parece que una estrategia de recuperación de errores tiene que ser un compromiso cuidadosamente considerado, teniendo en cuenta la clase de errores que se pueden presentar y que sean de procesamiento razonable. Algunos compiladores intentan reparar el error proceso en el cual el compilador intenta adivinar lo que pretendía escribir el programador.

Hay muchas estrategias generales distintas que puede emplear un analizador sintáctico para recuperarse de un error sintáctico. Aunque ninguna de ellas ha demostrado ser la aceptación universal, algunos métodos tienen una amplia aplicabilidad. Mencionaremos algunas estrategias, entre las cuales están:

Recuperación en modo de pánico: Este es el método más sencillo de implantar y pueden utilizarlo la mayoría de los métodos de análisis sintáctico. Al descubrir un error, el analizador sintáctico desecha símbolo de entrada, de uno en uno, hasta que encuentra uno perteneciente a un conjunto designado de componente léxicos de sincronización. Estos componente léxicos de sincronización son generalmente delimitadores, como el punto y coma o la palabra clave end, cuyo papel en el programa fuente está claro. Es evidente que quien diseña el compilador debe seleccionar los componentes léxicos de sincronización adecuados para el lenguaje fuente. Aunque la corrección en modo de pánico a menudo omite una cantidad considerable de entrada sin comprobar la existencia de errores adicionales, tiene la ventaja de la sencillez y, a diferencia de otros métodos, esta garantizado contra lazos infinitos. En situaciones en donde son raros los errores múltiples en la misma proposición, este método puede resultar bastante adecuado.

LIC. MARTHA MARTINEZ MORENO46

Page 47: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Recuperación a nivel de frase: Al descubrir un error, el analizador sintáctico puede realizar una corrección local de la entrada restante; es decir, puede sustituir un prefijo de la entrada restante por alguna cadena que permita continuar al analizador sintáctico. Una corrección local típica sería sustituir una coma por un punto y coma, suprimir un punto y coma sobrante e insertar un punto y coma que falta. La elección de la corrección local corresponde al diseñador del compilador. Por supuesto se debe de tener cuidado de elegir sustituciones que no conduzcan a lazos infinitos.

Este tipo de sustitución puede corregir cualquier cadena de entrada y ha sido empleado en varios compiladores que corrigen los errores. El método se usó por primera vez en el análisis sintáctico descendente. Su principal desventaja es su dificultad para afrontar situaciones en que el error real se produjo antes del punto de detección.

Producciones de error: Si se tiene una buena idea de los errores comunes que puede encontrarse, se puede aumentar la gramática del lenguaje con producciones que generen la construcciones erróneas. Entonces se usa esta gramática aumentada con las producciones de error para construir el analizador sintáctico. Si el analizador sintáctico usa una producción de error, se pueden generar diagnósticos de error apropiados para indicar la construcción errónea reconocida en la entrada.

Corrección global: Idealmente, sería deseable que un compilador hiciera el mínimo de cambios posibles al procesar una cadena de entrada incorrecta. Existen algoritmos para elegir una secuencia mínima de cambios para obtener una corrección global de menor costo. Dada una cadena de entrada incorrecta x y la gramática G, estos algoritmos encontrarán un árbol de análisis sintáctico para una cadena relacionada y, tal que el número de inserciones, supresiones y modificaciones de componentes léxicos necesarios para transformar x en y sea el mínimo posible. Por desgracia, la implantación de estos métodos es en general demasiado costosa en términos de tiempo y espacio, así que estas técnicas en la actualidad sólo son de interés teórico.

Se debe señalar que un programa correcto más parecido al original puede no ser lo que el programador tenía en mente. Sin embargo, la noción de corrección de costo mínimo proporciona una escala para evaluar las técnicas de recuperación de errores, y se ha

LIC. MARTHA MARTINEZ MORENO47

Page 48: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

usado para encontrar cadenas de sustitución óptimas para recuperación a nivel de frase.

Gramáticas.

Las gramáticas describen lenguajes. Los lenguajes naturales como el español o el ingles, son con frecuencia descritos por una gramática que agrupa las palabras en categorías sintácticas tales como sujetos, predicados, frases preposicionales, etc. 

Expresándolo en forma matemática, una gramática es un dispositivo formal para expresar un lenguaje potencialmente infinito, en una manera finita, puesto que es imposible enumerar todas las cadenas de caracteres en un lenguaje, ya sea español inglés o Pascal. Al mismo tiempo un gramática impone una estructuras a la sentencias en el lenguaje. Es decir una gramática, G, define un lenguaje L(G) mediante la definición de una manera de para derivar todas las cadenas en el lenguaje.

De una manera más general:

Una gramática es un sistema para definir un lenguaje, como también una herramienta para imponer a las cadenas del lenguaje una estructura útil. 

Una gramática G es una 4-tupla G = {N,,P, S} donde: 

N : alfabeto de no-terminales "" n Î N à n Ï   : alfabeto terminal P : Conjunto finito de producciones consistente de expresiones de la forma a b ® con a Î (N È )+ y b Î(N È)* S : símbolo inicial S Î N NÇS = f

GRAMÁTICAS Y LENGUAJES SENSIBLES AL CONTEXTO.

Una  forma  de definir  un  lenguaje es mediante un reconocedor que acepte las palabras que pertenecen a él. Los lenguajes sensibles al contexto contienen, propiamente, a los lenguajes independientes al contexto a su vez, los lenguajes recursivos contienen propiamente a los lenguajes sensibles al contexto.

LIC. MARTHA MARTINEZ MORENO48

Page 49: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

DEFINICION FORMAL MATEMATICA DE LAS GRAMÁTICAS SENSIBLES AL CONTEXTO (CSG’S)

Es una 4-TUPLA formada por (N, S ,S ,P) donde:

N: Colección de símbolos no terminales. : Alfabeto del lenguaje. S: Símbolo especial llamado símbolo inicial. P: Conjunto de producciones si todas las producciones son de la forma b®a,

           donde a, b e (N U )+ y | a | <= | b |.  El conjunto de los lenguajes sensibles al contexto contiene el conjunto de los lenguajes independientes del contexto. La restricción de que el lado derecho de las producciones en una gramática sensible al contexto sea al menos tan largo como el lado izquierdo hace que la gramática sea no contráctil. Puesto que la cadena vacía tiene longitud cero, podemos deducir de la definición que Ï L(G), para cualquier gramática que es sensible al contexto. A veces es conveniente que la cadena pertenezca al lenguaje que ha sido generado por una gramática sensible al contexto.

Clasificación de las Gramáticas.

Lenguaje Gramática Autómata Ejemplo

Lenguajes Gramática RegularDetector

deterministico ó no deterministico finito

a*

Regulares

+ Gramática-lineal Derecha        +

Gramática-lineal Izquierda

Lenguaje librede contexto

+ Gramática libre de contexto

Autómata pushdown no

deterministicoab

Lenguaje sensible al contexto

+Gramática Sensible al

Autómata Lineal-Vinculada

abc

LIC. MARTHA MARTINEZ MORENO49

Page 50: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

contexto

Lenguajes Recursivos

Enumerables

+ Gramática no-restringida

Maquina de TuríngCualquier Función

Computable

Jerarquía de Chomsky.En 1959, Noam Chomsky clasificó las gramáticas en cuatro tipos

de lenguajes y esta clasificación es conocida como la jerarquía de Chomsky, tal como se muestra en al Tabla 3.1, en la cual cada lenguaje es descrito por el tipo de gramática generado. Estos lenguajes sirven como base para la clasificación de lenguajes de programación.

Los cuatro tipos son: lenguajes recursivamente enumerables, lenguajes sensibles al contexto, lenguajes libres de contexto y lenguajes regulares. Dichos lenguajes también se identifican como lenguajes de tipo 0,1,2 y 3.

Las gramáticas sensibles al contexto reciben el nombre de gramáticas de tipo 1, porque del lado izquierdo puede haber más de un elemento, lo que implica que un símbolo puede reemplazarse en el contexto de otros.  

Una gramática es de tipo 1 si se exige que la longitud del miembro derecho de toda regla de producción sea mayor o igual que la longitud del izquierdo. Con esto se impide que las cadenas que se obtengan en el transcurso de la aplicación de las reglas sean de longitud decreciente, y se impide así mismo el caso extremo de que mediante una gramática de tipo1 puedan desaparecer cadenas de símbolo.   

Gramática y lenguaje  Reconocedor Que genera Autómatas lineal  TIPO 1  (Sensible al contexto)  

Entre las gramáticas no restringidas y la forma restringida de las gramáticas independientes al contexto, se pueden definir varias gramáticas con diferentes niveles de restricción.

Existe una exacta correspondencia entre cada uno de estos tipos de lenguajes y particulares arquitecturas de máquinas en el sentido que por cada lenguaje tipo T hay una arquitectura de máquina A que reconoce el lenguaje A  hay un tipo T tal que todos los lenguajes

LIC. MARTHA MARTINEZ MORENO50

Page 51: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

reconocidos por A son de tipo T. La correspondencia entre lenguajes y arquitectura son mostrados en la siguiente tabla: 

Tipo LenguajesTipo de Máquina

0Recursivamente Enumerables

Máquina de Turing

1Sensibles al contexto

Autómata Lineal Acotado

2Libres de contexto

Autómatas de Pila

3Lenguajes Regulares

Autómatas Finitos y Expresiones Regulares

Los cuatro tipos de gramáticas.

Sobre un alfabeto dado, el conjunto de lenguajes recursivamente enumerables contiene propiamente al conjunto de lenguajes recursivos que contiene propiamente al conjunto de lenguajes sensibles al contexto que contiene propiamente al conjunto de lenguajes libres de contexto, que a su vez contiene propiamente a los lenguajes regulares, tal como se muestra:

LIC. MARTHA MARTINEZ MORENO51

Page 52: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Gramáticas Ambiguas

Si una oración en español tiene mas de un significado, se dice que es ambigua. Con frecuencia tales oraciones pueden analizarse sintácticamente en mas de una forma.        La oración:

El tiempo vuela como una flecha.

Puede interpretarse tiempo como sustantivo, vuela como verbo y una flecha, como una frase adverbial. Esta interpretación es un comentario acerca del rápido paso del tiempo. Sin embargo, si "tiempo" se interpreta como adjetivo, "vuela" como sustantivo, "como" como un verbo y flecha como un sujeto directo, la oración se convertiría en un comentario acerca de la vida amorosa de alguna especie.

En forma similar, se asigna significado a las construcciones en los lenguaje de programación con base en sus sintaxis. Por consiguiente,

LIC. MARTHA MARTINEZ MORENO52

Page 53: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

preferimos que las gramáticas de los lenguajes de programación describan programas sin ambigüedad alguna.

Una sentencia es ambigua si hay mas de una derivación distinta. Si una sentencia es ambigua, su árbol de análisis sintáctico no es único; podemos crear mas de un árbol sintáctico para la misma sentencia.

Una gramática se dice que es ambigua si hay dos o más árboles de derivación distintos para la misma cadena.

Para ver lo anterior consideraremos esta gramática. O lo que es lo mismo:

Es la gramática que produce mas de una derivación por la derecha o por la izquierda para la misma frase

S ® SbS | ScS | a

Podemos derivar la cadena "abaca" de dos formas distintas como sigue:     S Þ SbS Þ SbScS Þ SbSca Þ Sbaca Þ abaca       S Þ ScS Þ SbScS Þ abScS Þ abacS Þ abaca   El árbol de derivación para la derivación 1 es el que se muestra en la Figura:

LIC. MARTHA MARTINEZ MORENO53

Page 54: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Primer árbol de derivación para la gramática dada.mientras que el árbol para la derivación 2 es el que se muestra en la siguiente figura:

Segundo árbol de derivación para la gramática dada.

Obsérvese que los dos árboles son distintos, aunque las cadenas producida son la misma. La ambigüedad  puede ser un problema para ciertos lenguajes en los que su significado depende, en parte, de su estructura, como ocurre con los lenguajes naturales y los lenguajes de programación. Si la estructura de un lenguaje tienen más de una composición y si la construcción parcial determina su significado, entonces el significado es ambiguo.

    La ambigüedad no siempre es una propiedad inherente de los lenguajes; existen gramáticas ambiguas y no ambiguas para algunas construcciones.

Para demostrar que una  gramática es ambigua, lo único que hay que hacer es encontrar una cadena de componentes léxicos que tanga mas de un árbol sintáctico. Como una cadena que cuenta con mas de un árbol sintáctico suele tener mas de un significado, para aplicaciones de compilación es necesario diseñar gramáticas no ambiguas o utilizar gramáticas ambiguas con reglas adicionales para resolver las ambigüedades.

LIC. MARTHA MARTINEZ MORENO54

Page 55: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Recuérdese que una gramática independiente del contexto es ambigua si hay dos derivaciones por la izquierda que son distintas para la misma cadena. La gramática independiente del contexto:

S à ASB |SS | eB à b

Es ambigua, ya que hay dos derivaciones por la izquierda de a2b2.

Por desgracia, en general no es posible determinar si una gramática independiente del contexto es ambigua.  Es decir, la cuestión de la ambigüedad de gramáticas independientes del contexto.        CONCEPTOS:

SÍMBOLO TERMINAL: Es un símbolo que no aparece en el lado izquierdo de ninguna producción.

NO TERMINALES: Los no-terminales son los nodos no-hojas en un árbol de análisis sintáctico.

PRODUCCIONES: Se pueden pensar como un conjunto de reglas de reemplazo.

SÍMBOLO DE INICIO: El símbolo de partida o de inicio, también denominado símbolo meta es un no- terminal especial diseñado como el único a partir del cual todas las cadenas son derivadas.

FORMA SENTENCIAL: Una forma sentencial es cualquier cadena de caracteres derivada desde el símbolo de inicio.

SENTENCIA: una sentencia es una forma que consiste solamente de terminales tales como a + a * a

MÉTODOS DESCENDENTES O TOP-DOWN. El análisis sintáctico descendente crea una derivación más a la

izquierda, los pasos son:

LIC. MARTHA MARTINEZ MORENO55

Page 56: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

1) Comenzar con el símbolo de inicio como la “raíz” del árbol de análisis sintáctico.

2) En cada paso, reemplace el no terminal del lado izquierdo en la forma sentencial actual.

 Se puede considerar el análisis sintáctico descendente como un intento de encontrar una derivación por la izquierda para una cadena de entrada. Equivalentemente puede ser visto como el intento de construir un árbol de análisis sintáctico para la entrada comenzando desde la raíz y creando los nodos del árbol en orden previo. Por ejemplo considere la gramática:

S ® cAdA ® ab | a

y la cadena de entrada w = cad. Para construir un árbol de análisis sintáctico descendente para esta cadena, primero se crea un árbol formado por un solo nodo etiquetado con S. Un apuntador a la entrada apunta a c, el primer símbolo de w. Después se utiliza la primera producción de S para expandir el árbol y obtener el árbol de la Fig.(a).

Se empareja la hoja más a la izquierda, etiquetada con c, con el primer símbolo de w, y a continuación se aproxima el apuntador de entrada a a, el segundo símbolo de w, y se considera la siguiente hoja etiquetada con A. Entonces se puede expandir A utilizando la primera alternativa de A para obtener el árbol de la Fig. (b).

Como ya se tiene una concordancia para el segundo símbolo de la entrada, se lleva el apuntador de entrada a d, el tercer símbolo de la entrada, y se compara d con la hoja siguiente, etiquetada con b. Como b no concuerda con d, se indica fallo y se regresa a A para saber si existe otra alternativa de A que no se haya intentado, pero se puede dar lugar a un emparejamiento.

Al regresar a A, se debe restablecer el apuntador de entrada a la posición 2, aquella que tenia al ir a A por primera vez, se intenta a continuación la segunda alternativa de A para obtener el árbol de la Fig. (c).

 Se empareja la hoja a con el segundo símbolo de w, y la hoja d, con el tercer símbolo. Como ya se ha producido un árbol de análisis

LIC. MARTHA MARTINEZ MORENO56

Page 57: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

sintáctico par w, se para y se anuncia el éxito de la realización completa del análisis sintáctico.

 

Pasos en el Análisis Sintáctico Descendente.Análisis Sintáctico por Descenso Recursivo.

Un analizador sintáctico de descenso recursivo se compone de un procedimiento para cada símbolo no terminal de la gramática, cuando se llama un procedimiento este intenta encontrar una subcadena de la entrada, empezando por el componente léxico actual, que se pueda interpretar como el no terminal con el cuál está asociado al procedimiento.

Durante este proceso puede llamar a otros procedimientos o llamarse a sí mismo recursivamente, para buscar otros no terminales. Si un procedimiento encuentra el no terminal que pretende, devuelve una indicación con éxito a quién lo llamó, y también avanza el apuntador al componente léxico situado después de la subcadena que acaba de reconocer. Si el procedimiento es incapaz de encontrar una subcadena que pueda interpretarse como el no terminal deseado, devuelve una indicación de fallo o invoca a una rutina de diagnóstico y recuperación de errores.

LIC. MARTHA MARTINEZ MORENO57

Page 58: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

A modo de ejemplo considérese la siguiente gramática:

<lect> ::=READ(<lista_id)><lista_id> ::=id | <lista_id>,id

que definen la sintaxis de la proposición READ del lenguaje PASCAL.

El procedimiento para <lect> es un analizador sintáctico de descenso recursivo examina primero los 2 siguientes componentes léxicos de entrada buscando READ y (. Si se encuentran, entonces el procedimiento para <lect> llama el procedimiento para <lista_id>. Si ese procedimiento tiene éxito, el procedimiento <lect>, examina el siguiente componente léxico buscando un ). Si todas estas pruebas tienen éxito el procedimiento <lect> devuelve una indicación de éxito a quien lo llama y avanza al próximo componente léxico que sigue a ). En otro caso el procedimiento devuelve una indicación de fallo.

El procedimiento es ligeramente más complicado cuando hay varias opciones definidas por la gramática para un no terminal. En este caso el procedimiento debe decidir que opción intentar. Para la técnica de descenso recursivo, se debe poder decidir qué opción utilizar examinando el siguiente componente léxico de entrada. Hay otros métodos descendentes que eliminan este requisito, aunque no son tan eficientes como el descendente recursivo.

Si se intentara escribir los procedimientos para la gramática anterior, se descubriría un problema. El procedimiento para <lista_id>, sería incapaz de decidir entre sus dos opciones, pues tanto id, como <lista_id> pueden comenzar con id. Sin embargo hay una dificultad más importante, si el procedimiento hubiera decidido intentar la segunda opción <(lista_id,id)>, se llamaría de inmediato a si mismo recursivamente para encontrar una <lista_id>. Esto podría producir otra llamada recursiva inmediata, que daría lugar a una cadena sin fin. La razón de esto es que una de las opciones para <lista_id> empieza con <lista_id>. Los analizadores sintácticos descendentes no pueden usarse directamente con una gramática que contenga esta clase de recursión por la izquierda inmediata.

LIC. MARTHA MARTINEZ MORENO58

Page 59: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

A continuación se muestra la gramática anterior por la recursión por la izquierda eliminada:

<lect> ::= READ(<lista_id>)<lista_id> ::= id<mas_id><lista_id> ::= , id <mas_id>|e

Pudiéndose representarse también así:

<lect> ::= READ(<lista_id>)<lista_id> ::= id{,id}

Esta notación, que es una extensión común a BNF, especifica que los términos entre { y } se pueden omitir o repetir una o mas veces. De esta forma la gramática define a <lista_id> compuesta de un id seguida de cero o mas ocurrencias de ",id". Con la definición revisada, el procedimiento para <lista_id> simplemente busca primero un id, y después sigue explorando la entrada mientras los dos siguientes componentes léxicos sean una como(,) e id. Esto elimina el problema de la recursión por la izquierda y también la dificultad de decidir que opción intentar para <lista_id>.

LIC. MARTHA MARTINEZ MORENO59

Page 60: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Análisis Sintáctico descendente recursivo de una proposición READ.

La Fig. anterior ilustra el análisis sintáctico descendente recursivo de la proposición READ, con la gramática anterior. En el apartado (1), se ha llamado el procedimiento LECT y se han examinado los componentes léxicos READ y "(" del flujo de entrada (indicado con líneas punteadas). En el apartado (2), LECT ha llamado a LISTA_ID (indicado con línea continua), que ha examinado el componente id. En el apartado (3), LISTA_ID ha vuelto a LECT indicando que ha tenido éxito; por lo que LECT ha examinado el componente léxico de entrada ), con esto se completa el análisis de la proposición fuente. El procedimiento READ regresara ahora a quién lo llamó, indicando que se encontró satisfactoriamente un <lect>. Obsérvese que la secuencia de llamadas a procedimientos y la exploración de componentes léxicos ha definido por completo la estructura de la proposición READ.

LIC. MARTHA MARTINEZ MORENO60

Page 61: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Ejemplo Descendente RecursivoGramática

               

Programa® Sentencias

               Sentencias

® Sentencia Sentencias'

               Sentencias'

® Sentencias | e

               Sentencia

® Asignación ;

               Asignación

® Id := Expresión

               Expresión

® Termino Expresión'

               Expresión'

® + Termino Expresión'  | e

               Termino

® Factor Termino'

               Termino'

® * Factor Termino'  | e

               Factor

® ( Expresión )

| Id| Literal

 

Análisis sintáctico Predictivo.

Un analizador sintáctico predictivo, llamado también análisis sintáctico descendente recursivo es un método descendente en el que se ejecuta un conjunto de producciones para procesar una entrada, a cada símbolo no terminal de una gramática se le asocia con una producción. Es una forma eficiente de implementar el análisis sintáctico descendente implícitamente manteniendo una pila, en vez de hacerlo implícitamente mediante llamadas recursivas. El modelo sintáctico predictivo puede verse en la figura de abajo. Un analizador sintáctico es guiado por una tabla en la que se encuentran las producciones.

LIC. MARTHA MARTINEZ MORENO61

Page 62: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Modelo de un analizador sintáctico predictivo.           

Características:Es una forma eficiente de un analizador sintáctico recursivo.Mantiene una PILA en vez de hacer llamadas recursivas.El analizador sintáctico predictivo tiene una ENTRADA, una

TABLA de análisis sintáctico y una SALIDA, además de la PILA.La Pila contiene una secuencia de símbolos de alguna gramática

que debe de finalizar con un $ (delimitador).El elemento inicial de la gramática debe de ser la cima de la

PILA.La TABLA es un arreglo bidimensional (matriz), M

[símbolo_no_terminal , símbolo_terminal_$]

El analizador sintáctico esta controlado por un programa que trabaja como sigue. El programa determina a X, el símbolo en la cima de la pila, y a, el símbolo de la entrada actual. Estos dos símbolos determinan la acción del analizador sintáctico: hay tres posibilidades:

1.Si X = a = $, el analizador sintáctico se detiene y anuncia el éxito de la realización del análisis sintáctico.

2.Si X = a¹$, el analizador sintáctico saca a X dela pila y avanza el apuntador de entrada al siguiente símbolo de entrada.

3.Si X es un símbolo no terminal el programa consulta la entrada M[X,a] de la tabla de análisis sintáctico. Esta entrada podría ser una

LIC. MARTHA MARTINEZ MORENO62

Page 63: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

producción cualquiera de la gramática o un error. Si M[X,a] = {X®UVW}, el analizador sintáctico reemplaza a X en la cima de la pila por WVU (con U en la cima). Si M[X,a] = error el analizador sintáctico llama a una rutina de recuperación de error.

Se puede describir el comportamiento del analizador sintáctico en términos de su configuración, la cual es dada por el contenido de la pila y la entrada restante. Inicialmente el analizador sintáctico esta en la configuración:

Pila Entrada$S w$

donde S es el símbolo de inicio dela gramática y w es la cadena que ha de ser analizada.

Gramática

E ® TE' E' ® +TE' | x T ® FT' T' ® *FT | x F ® (E) | id

 Algoritmo1.      Si X = a = $, el analizador sintáctico se detiene y anuncia el

éxito de la realización del análisis sintáctico. 2.      Si X = a ¹ $,el analizador sintáctico saca a X de la pila y

avánzale apuntador de entrada al siguiente símbolo de entrada. 3.      Si X es un símbolo no terminal el programa consulta la

entrada M[X,a] de la tabla de análisis sintáctico. Esta entrada podría ser una producción cualquiera dela gramática o un error. Si M[X,a] = {X®UVW}, el analizador sintáctico reemplaza a X en la cima dela pila por WVU (con U en la cima). Si M[X,a] = error el analizador sintáctico llama a una rutina de recuperación de error.

Se puede describir el comportamiento del analizador sintáctico en términos de su configuración, la cual es dada por el contenido de la pila y la entrada restante. Inicialmente el analizador sintáctico esta en la configuración:  

Pila Entrada$S w$

donde S es el símbolo de inicio dela gramática y w es la cadena que ha de ser analizada.

LIC. MARTHA MARTINEZ MORENO63

Page 64: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Matriz de precedencia resultante 

id + * ( ) $

E E ® TE´ E ® TE´

E´E´® +TE

´E´® x E´® x

T T ® FT´ T ® FT´

T´ T´ ® x T´® *FT´ T´® x T´x®

F F ® id F ® ( E )

 Análisis

Se analizará la cadena siguiente: id + id * id $

Paso 1 Inicialmente la pila contiene el símbolo de inicio de la gramática precedido por el delimitador "$", así que tenemos en la pila a "$E". Y en la entrada tenemos a la cadena completa.

Paso Pila Entrada Salida

1 $ Eid + id * id

$

Paso 2 En la cima de la pila tenemos a "E" verificamos en la gramática y tenemos que es un no terminal, ocupamos la regla 3 del algoritmo, y reemplazamos X = E por su producción la cual es TE'. Introduciremos la producción en el orden inverso, y tendremos en la pila "$ E' T", y la salida será la producción del no terminal "E".

Paso 3 En la cima de la pila tenemos a "T" verificamos en la gramática y tenemos que es un no terminal, ocupamos la regla 3 del algoritmo, y reemplazamos X = T por su producción la cual es FT'. Introduciremos la producción en el orden inverso, y tendremos en la pila "$ E' T' F", y la salida será la producción del no terminal "T".

LIC. MARTHA MARTINEZ MORENO64

Page 65: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Paso 4 Como la cima de la pila esta X= F y este produce el símbolo de la cadena leída, se sustituye por su producción que es el terminal "id" y avanza el apuntador de entrada al siguiente símbolo de la cadena, ahora  tendremos en la pila "$ E' T' id", y la salida será la producción de "F".

Paso 5 Como "id" es igual al símbolo de entrada actual se compara y se saca de la pila y avanza el apuntador al siguiente símbolo de entrada.

  Paso 6 En la cima de la pila tenemos a "T'" verificamos en la gramática y tenemos que es un no terminal, ocupamos la regla 3 del algoritmo, y reemplazamos X = T' por su producción la cual es x. Introduciremos la producción en el orden inverso, y tendremos en la pila "$ E' " ó "$ E' x", y la salida será la producción del no terminal " T' ".

Paso 7 En la cima de la pila tenemos a " E' " verificamos en la gramática y tenemos que es un no terminal, ocupamos la regla 3 del algoritmo, y reemplazamos X = E' por su producción la cual es +TE'. Introduciremos la producción en el orden inverso, y tendremos en la pila "$ E' T +" y la salida será la producción del no terminal " E' ".

  Paso 8 Como "+" es igual al símbolo de entrada actual se compara y se saca de la pila y avanza el apuntador al siguiente símbolo de entrada.

Paso 9 En la cima de la pila tenemos a " T " verificamos en la gramática y tenemos que es un no terminal, ocupamos la regla 3 del algoritmo, y reemplazamos X = T por su producción la cual es FT'. Introduciremos la producción en el orden inverso, y tendremos en la pila "$ E' T' F" y la salida será la producción del no terminal " T ".

  Paso 10 Como la cima de la pila esta X= F y este produce el símbolo de la cadena leída, se sustituye por su producción que es el terminal "id" y avanza el apuntador de entrada al siguiente símbolo de la cadena,

LIC. MARTHA MARTINEZ MORENO65

Page 66: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

ahora  tendremos en la pila "$ E' T' id", y la salida será la producción de "F".

Paso 11 Como "id" es igual al símbolo de entrada actual se compara y se saca de la pila y avanza el apuntador al siguiente símbolo de entrada.

  Paso 12 En la cima de la pila tenemos a " T' " verificamos en la gramática y tenemos que es un no terminal, ocupamos la regla 3 del algoritmo, y reemplazamos X = T' por su producción la cual es *FT'. Introduciremos la producción en el orden inverso, y tendremos en la pila "$ E' T' F *" y la salida será la producción del no terminal " T' ".

  Paso 13 Como "*" es igual al símbolo de entrada actual se compara y se saca de la pila y avanza el apuntador al siguiente símbolo de entrada.

  Paso 14 Como la cima de la pila esta X= F y este produce el símbolo de la cadena leída, se sustituye por su producción que es el terminal "id" y avanza el apuntador de entrada al siguiente símbolo de la cadena, ahora  tendremos en la pila "$ E' T' id", y la salida será la producción de "F".

Paso 15 Como "id" es igual al símbolo de entrada actual se compara y se saca de la pila y avanza el apuntador al siguiente símbolo de entrada.

Paso 16 En la cima de la pila tenemos a " T' " verificamos en la gramática y tenemos que es un no terminal, ocupamos la regla 3 del algoritmo, y reemplazamos X = T' por su producción la cual es x. Introduciremos la producción en el orden inverso, y tendremos en la pila "$ E'" ó "$ E' x" y la salida será la producción del no terminal " T' ".

  Paso 17 En la cima de la pila tenemos a " E' " verificamos en la gramática y tenemos que es un no terminal, ocupamos la regla 3 del algoritmo, y reemplazamos X = E' por su producción la cual es x. Introduciremos la

LIC. MARTHA MARTINEZ MORENO66

Page 67: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

producción en el orden inverso, y tendremos en la pila "$ E'" ó "$ E' x" y la salida será la producción del no terminal " T' ".

 

Paso Pila Entrada Salida

1 $ E id + id * id $

2 $ E' T id + id * id $ E ® TE´

3 $ E' T' F id + id * id $ T ® FT´

4 $ E' T' Id id + id * id $ F ® id

5 $ E' T' + id * id $

6 $ E' + id * id $ T´ ® x7 $ E' T + + id * id $ E´® +TE´

8 $ E' T id * id $

9 $ E' T' F id * id $ T ® FT´

10 $ E' T' Id id * id $ F ® id

11 $ E' T' * id $

12 $ E' T' F * * id $ T´® *FT´

13 $ E' T' F id $

14 $ E' T' Id id $ F ® id

15 $ E' T' $

16 $ E' $ T´® x17 $ $ E´® x

En la cima de la pila tenemos a " $ " y este es igual al símbolo leído y a su vez es igual al delimitador, entonces el analizador sintáctico se detiene y anuncia el éxito de la realización del análisis de la cadena.

MÉTODOS ASCENDENTES O BOTTOM UP.

El análisis sintáctico por desplazamiento intenta construir un árbol de análisis sintáctico para una cadena de entrada que comienza por las hojas (el fondo) y avanza hacia la raíz (la cima). Se puede considerar este proceso como de "reducir" una cadena w al símbolo inicial de la gramática. En cada paso de reducción se sustituye una subcadena determinada que concuerde con el lado derecho de una producción por

LIC. MARTHA MARTINEZ MORENO67

Page 68: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

el símbolo del lado izquierdo de dicha producción y si en cada paso se elige correctamente la subcadena, se traza una derivación por la derecha en sentido inverso.

Considérese la siguiente gramática: S ® aABe A ® Abc | bB ® d

La frase "abbcde" se puede reducir a S por los siguientes pasos:abbcde aAbcde aAde aABe S

Se examina abbcde buscando una subcadena que concuerde con el lado derecho de alguna producción. La subcadenas b y d sirven. Elíjase la b más situada a la izquierda y sustitúyase por A, el lado izquierdo de la producción A ® b; así se obtiene la cadena aAbcde. A continuación, las subcadenas Abc, b y d concuerdan con el lado derecho de alguna producción. Aunque b es la subcadena situada más a la izquierda que concuerda con el lado derecho de una producción, se elige sustituir la subcadena Abc por A, que es el lado derecho de la producción A ® Abc. Se obtiene ahora aAde. Sustituyendo después d por B que es el lado izquierdo de la producción B ® d, se obtiene aABe, ahora se puede sustituir toda la cadena por S. Por tanto, mediante una secuencia de cuatro reducciones se puede reducir abbcde a S. De hecho, estas reducciones trazan la siguiente derivación por la derecha en orden inverso:

            S Þ aABe Þ aAde Þ aAbcde Þ abbcde

Análisis Sintáctico de Precedencia Simple.Este método realiza el análisis sintáctico buscando rápidamente el

mango (frase simple mas a la izquierda) u de la forma sentencial actual y reduciéndola a un no terminal U, usando una regla U ® u . El problema en cualquier analizador sintáctico ascendente es encontrar el mango, entonces saber a que no terminal se reducirá.

LIC. MARTHA MARTINEZ MORENO68

Page 69: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Dada una forma sentencial X, considerar dos símbolos R y S en el vocabulario V de una gramática G . Suponer que hay una forma sentencial ...RS... . En algún punto cualquiera R o S (o ambas), deben de estar en un mango. Las siguientes tres posibilidades surgirían:

R es parte de un mango pero S no Fig. (a). En este caso escribimos R > S y decimos que R es mas grande que S, o que R tiene precedencia sobre S, porque deberá ser reducido primero. Note que R deberá ser el símbolo final de alguna regla U ® ...R. Note que debido a que el mango esta a la izquierda de S, S deberá ser un terminal.

R y S están ambos en un mango Fig.(b). Decimos que R ± S, ellos tienen la misma precedencia y deberán ser reducidos al mismo tiempo. Obviamente deberá existir una regla U ® ...RS... en la gramática. S es parte de un mango pero R no Fig. (c), decimos que R < S o que R es menor que S y S deberá ser la cabeza de alguna regla U ® S... Si no hay una función sentencial ...RS... entonces decimos que no hay relación entre el par ordenado(R,S).

Ilustración de las Relaciones de Precedencia.Algoritmo de Análisis de Precedencia Simple.

Cuando se usan las relaciones de precedencia para reconocer sentencias, lo mejor es usar una forma compacta para representarlas, la técnica usual es tener la matriz P con los valores:

     P[i,j] = 0        si no existe relación entre Si y Sj

           P[i,j] = 1        si Si < Sj

LIC. MARTHA MARTINEZ MORENO69

Page 70: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

           P[i,j] = 2        si Si = Sj            P[i,j] = 3        si Si > Sj

Podemos hacer esto para una gramática de precedencia porque sabemos que a lo sumo una relación existe entre los entre dos símbolos cualquiera.

Las reglas deben estar en una tabla, estructurada de tal forma que, dado un lado derecho, podamos encontrarlo en las reglas y encontrar el correspondiente lado izquierdo.

El algoritmo trabaja como sigue: Los símbolos de la cadena de entrada son procesados de izquierda a derecha y almacenados en una pila S, hasta que la relación de precedencia > exista entre el símbolo de la cima de la pila y el siguiente símbolo entrante. Esto significa que el símbolo de la cima de la pila es el final del mango, y por lo tanto el mango entero esta en la pila. Este mango es entonces encontrado en la lista de reglas y reemplazado en la pila, por el correspondiente no terminal el cual debe ser reducido. El proceso se repite hasta que la pila contenga Z y el siguiente símbolo de entrada sea $ (delimitador). Cada forma sentencial está encerrada entre los símbolos $ y $ (asumiendo que $ no es un símbolo de la gramática). Además se establece que $ < S y S > $ para cualquier símbolo S de la gramática.

En la Fig. se muestra un diagrama de flujo para el analizador sintáctico usando la siguiente notación:

S es una pila usada para mantener los símbolos; su contador es i.j es un índice usado para hacer referencia  los elementos en la

cima de la pila.La sentencia a ser analizada sintácticamente es TiT2...Tn.

Empezamos con el delimitador de la sentencia $ en la pila y asumimos que ha sido anexada a la sentencia como T[n+1].

Q y R son variables para mantener ciertos símbolos durante el proceso de análisis.

Note que la cadena T1...Tn no es una sentencia, entonces el algoritmo parará e indicará esto.

LIC. MARTHA MARTINEZ MORENO70

Page 71: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Analizador sintáctico de precedencia simple. BLOQUE 1: Inicializar la pila S para mantener el delimitador de

sentencia ($). Poner el índice de la sentencia apuntando al primer símbolo.

BLOQUE 2: Obtener el siguiente símbolo, colocarlo en R e incrementar k.

 BLOQUE 3 - 4: Si (S(i),R) no esta en >, el mango no esta

completamente en la pila, entonces metemos R en la pila y obtenemos el siguiente símbolo.

 BLOQUE 5 - 7: Si S(i) >R, el mango deberá estar en la pila. Estos

bloques buscan la cabeza del mango. 

LIC. MARTHA MARTINEZ MORENO71

Page 72: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

BLOQUE 8 - 9 :Checar la cadena S(j)...S(i). Si no esta en la parte derecha de una regla, el proceso ha terminado. La cadena fue una sentencia si y solamente si 1 = 2 y S(i) = Z. Si es la parte derecha de una regla, borramos el mango de la pila (bloque 9), metemos en la pila el símbolo el cual es reducido y regresamos al bloque 3 para buscar el siguiente mango.

Una ventaja acerca de este y otros analizadores sintácticos similares es que la cadena completa de los símbolos de entrada no necesitan estar en memoria al mismo tiempo. Los símbolos son leídos uno a la vez de la entrada y almacenados en la pila, pero como el mango es reducido, estos desaparecen. Solamente si el mango está en el final derecho de la cadena necesita que la cadena completa esté almacenada.

Ejemplo

GramáticaE     ® E + T | TT     ® T * F | FF     ® ( E ) |  id 

Matriz de precedencia resultante 

  E T F + * ( ) id

E       =     =  

T       > =   >  

F       > >   >  

+   = <     <   <

*     =     <   <

( = < <     <   <

)       > >   >  

id       > >   >  

  

AnálisisSe analizará la cadena siguiente:                                            ( x + y )

LIC. MARTHA MARTINEZ MORENO72

Page 73: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Paso

PilaRelaci

ónR

Token

0 $ < (x+y)

$

1 $( < x +y)$

2 $(x > + y)$

3 $(E = + y)$

4 $(E+ < y )$

5$

(E+y> ) $

6$

(E+T> ) $

7 $(E = ) $

8 $(E) > $ $

Análisis Sintáctico LR.

 El nombre de esta técnica de análisis sintáctico LR(k) proviene de “L” por el exámen de la entrada de izquierda a derecha (en inglés Left-to-Right), la “R” por construir una derivación por la derecha (en inglés rightmost derivation) en orden inverso, y la k por el número de símbolos de entrada de examen por anticipado utilizados para tomar las decisiones del análisis sintáctico. Cuando ésta se omite, se asume que k es 1. Esta técnica de análisis sintáctico resulta conveniente por varias razones:

 Se puede construir analizadores sintácticos LR para reconocer prácticamente todas las construcciones de los lenguajes de programación para los que se pueden escribir gramáticas libres de contexto.

El método de análisis sintáctico LR es el método de análisis por desplazamiento y reducción sin retroceso más general que se conoce y sin embargo se puede aplicar tan eficientemente como los otros métodos de desplazamiento y reducción.

La clase de gramáticas que pueden analizarse con los métodos LR es un supraconjunto de la clase de gramáticas que se puedan analizar con analizadores sintácticos predictivos.

Un analizador sintáctico predictivo LR puede detectar un error sintáctico tan pronto como sea posible hacerlo en un examen de izquierda a derecha de la entrada.

LIC. MARTHA MARTINEZ MORENO73

Page 74: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

El principal inconveniente del método es que supone demasiado trabajo construir un analizador sintáctico LR a mano para una gramática de un lenguaje de programación típico. Se necesita una herramienta especializada: un generador de analizadores sintácticos LR, por ejemplo YACC. Con este generador se puede escribir una gramática libre de contexto y el generador produce automáticamente un analizador sintáctico para dicha gramática .

  

Modelo de un analizador sintáctico LR.

 En la Fig se muestra de forma esquemática de un analizador sintáctico LR. Consta de una entrada, una salida, una pila, un programa conductor y una tabla de análisis sintáctico de dos partes (acción, ir_a). El programa conductor es el mismo para todos los analizadores sintácticos LR; solo cambian las tablas de un analizador a otro. La tabla de análisis sintáctico consta de dos partes, la función acción que indica una acción del analizador, y la función ir_a, que indica las transiciones entre los estados.

Realización

Gramática E ® E + T E ® T

LIC. MARTHA MARTINEZ MORENO74

Page 75: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

T ® T * F T ® F F ® ( E ) F ® id

Algoritmoinicializar la pila al estado cero

agregar $ al final de la entradaapuntar al primer símbolo de la entradarepetir begin             sea s el estado de la cima de la pila y              a el símbolo de entrada actual             if acción [s,a] = desplazar # then             begin                         meter a y después # (el nuevo estado) en la pila                         avanzar al siguiente símbolo de entrada             end             else if acción [s,a] = reducir A ® b then             begin                         sacar 2*| b | símbolos de la pila                         sea S’ el estado que ahora esta en la cima de la pila                         meter a y después ir_a[s’,a] en la pila.             end             else if acción[s,a] = aceptar then                       Return             else error()end.

 Matriz de precedencia resultante

 

LIC. MARTHA MARTINEZ MORENO75

Page 76: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

 Análisis

Inicializa la pila al estado cero, agregar el símbolo “$” al final de la cadena de entrada, paso 1:

id*(id+id)              

Pila EntradaAcció

nS a

(1)   0id*(id+id)

$0 id

 Como [S,a] = [0,id], tenemos a S5 que es = desplazar, entonces

se mete a (id) y después a # (5) (el nuevo estado) en la pila, avanzar al

siguiente símbolo, paso 2:  Verificando en la tabla tenemos (R6), como [S,a] = [5,*] = reducir

A ® b, b = 1 tenemos que sacar 2*|b|, ahora el tope de la pila es 0 y F el lado izquierdo de la producción 6, meter A que es F y el 3 obtenido en la intersección 0,F, paso 3:

  Verificando en la tabla tenemos (R4), como [S,a] = [3,*] = reducir

A ® b, b = 1 tenemos que sacar 2*|b|, ahora el tope de la pila es 0 y T el

LIC. MARTHA MARTINEZ MORENO

EstadoAcción ir_a

id + * ( ) $ E T F0 d5 d4 1 2 31 d6 Aceptar2 r2 d7 r2 r23 r4 r4 r4 r44 d5 d4 8 2 35 r6 r6 r6 r66 d5 d4 9 3

7 d5 d410

8 d6 d119 r1 d7 r1 r110 r3 r3 r3 r311 r5 r5 r5 r5

76

Page 77: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

lado izquierdo de la producción 4, meter A que es T y el 2 obtenido en la intersección 0,T, paso 4:

  Verificando en la tabla tenemos (S7), como [S,a] = [2,*] =

desplazar, entonces se mete a(*) y después a # (7) (el nuevo estado) en la pila, avanzar al siguiente símbolo, paso 5:

 Verificando en la tabla tenemos (S4), como [S,a] = [7,(] =

desplazar, entonces se mete a(“(“) y después a # (4) (el nuevo estado) en la pila, avanzar al siguiente símbolo, paso 6:

 Verificando en la tabla tenemos (S5), como [S,a] = [4,id] =

desplazar, entonces se mete a(id) y después a # (5) (el nuevo estado) en la pila, avanzar al siguiente símbolo, paso 7:

 Verificando en la tabla tenemos (R6), como [S,a] = [5,+] = reducir

A ® b, b = 1 tenemos que sacar 2*|b|, ahora el tope de la pila es 4 y F el lado izquierdo de la producción 6, meter A que es F y el 3 obtenido en la intersección 4,F, paso 8:

 Verificando en la tabla tenemos (R4), como [S,a] = [3,+] = reducir

A ® b, b = 1 tenemos que sacar 2*|b|, ahora el tope de la pila es 4 y T el lado izquierdo de la producción 4, meter A que es T y el 2 obtenido en la intersección 4,T, paso 9:

Verificando en la tabla tenemos (R2), como [S,a] = [2,+] = reducir A ® b, b = 1 tenemos que sacar 2*|b|, ahora el tope de la pila es 4 y E el lado izquierdo de la producción 2, meter A que es E y el 8 obtenido en la intersección 4,E, paso 10:

 Verificando en la tabla tenemos (S6), como [S,a] = [8,+] =

desplazar, entonces se mete a(+) y después a # (6) (el nuevo estado) en la pila, avanzar al siguiente símbolo, paso 11:

 Verificando en la tabla tenemos (S5), como [S,a] = [6,id] =

desplazar, entonces se mete a(id) y después a # (5) (el nuevo estado) en la pila, avanzar al siguiente símbolo, paso 12:

 Verificando en la tabla tenemos (R6), como [S,a] = [5,)] = reducir

A ® b, b = 1 tenemos que sacar 2*|b|, ahora el tope de la pila es 6 y F el

LIC. MARTHA MARTINEZ MORENO77

Page 78: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

lado izquierdo de la producción 6, meter A que es F y el 3 obtenido en la intersección 6,F, paso 13:

 Verificando en la tabla tenemos (R4), como [S,a] = [3,)] = reducir

A ® b, b = 1 tenemos que sacar 2*|b|, ahora el tope de la pila es 6 y T el lado izquierdo de la producción 4, meter A que es T y el 9 obtenido en la intersección 6,T, paso 14:

 Verificando en la tabla tenemos (R1), como [S,a] = [9,)] = reducir

A ® b, b = 3 tenemos que sacar 2*|b|, ahora el tope de la pila es 4 y E el lado izquierdo de la producción 1, meter A que es E y el 8 obtenido en la intersección 4,E, paso 15:

 Verificando en la tabla tenemos (S11), como [S,a] = [8,)] =

desplazar, entonces se mete a(“)“) y después a # (11) (el nuevo estado) en la pila, avanzar al siguiente símbolo, paso 16:

 Verificando en la tabla tenemos (R5), como [S,a] = [11,$] =

reducir A ® b, b = 3 tenemos que sacar 2*|b|, ahora el tope de la pila es 7 y F el lado izquierdo de la producción 5, meter A que es F y el 10 obtenido en la intersección 7,F, paso 17:

 Verificando en la tabla tenemos (R3), como [S,a] = [10,$] =

reducir A ® b, b = 3 tenemos que sacar 2*|b|, ahora el tope de la pila es 0 y T el lado izquierdo de la producción 3, meter A que es T y el 2 obtenido en la intersección 0,T, paso 18:

 Verificando en la tabla tenemos (R2), como [S,a] = [2,$] = reducir

A ® b, b = 1 tenemos que sacar 2*|b|, ahora el tope de la pila es 0 y E el lado izquierdo de la producción 2, meter A que es E y el 1 obtenido en la intersección 0,E, paso 19:

 

Pila Entrada Acción S a

(1)   0 id*(id+id)$ S5 0 id

(2)   0 id 5 *(id+id)$ R6 5 *

(3)   0 F 3 *(id+id)$ R4 3 *

(4)   0 T 2 *(id+id)$ S7 2 *

(5)   0 T 2 * 7 (id+id)$ S4 7 (

(6)   0 T 2 * 7 ( 4 id+id)$ S5 4 id

LIC. MARTHA MARTINEZ MORENO78

Page 79: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

(7)   0 T 2 * 7 ( 4 id 5 +id)$ R6 5 +

(8)   0 T 2 * 7 ( 4 F 3 +id)$ R4 3 +

(9)   0 T 2 * 7 ( 4 T 2 +id)$ R2 2 +

(10) 0 T 2 * 7 ( 4 E 8 +id)$ S6 8 +

(11) 0 T 2 * 7 ( 4 E 8 + 6 )$ S5 6 id

(12) 0 T 2 * 7 ( 4 E 8 + 6 id 5

)$ R6 5 )

(13) 0 T 2 * 7 ( 4 E 8 + 6 F 3

)$ R4 3 )

(14) 0 T 2 * 7 ( 4 E 8 + 6 T 9

)$ R1 9 )

(15) 0 T 2 * 7 ( 4 E 8 )$ S11 8 )

(16) 0 T 2 * 7 ( 4 E 8 ) 11 $ R5 11 )

(17) 0 T 2 * 7 F 10 $ R3 10 $

(18) 0 T 2 $ R2 2 $

(19) 0 E 1 $   1 $

 Verificando en la tabla tenemos (Aceptar), por lo tanto la cadena

ha sido completamente aceptada

Matrices de Transición. Una matriz de transición es una matriz o tabla M, cuyos renglones

representan las "cabezas" (cadenas de símbolos, las cuales terminan en un símbolo terminal) de los lados derechos de las reglas de las gramáticas, las cuales pueden aparecer en la pila, y cuyas columnas representan los símbolos terminales, incluyendo el delimitador $. Los elementos de la matriz serán números o direcciones de subrutinas.

La Tabla muestra la estructura de una matriz de transición para la siguiente gramática (parcialmente llena).

Gramática:<prog>        ::=   <estado> <estado>     ::=   IF <expr> THEN <estado> <estado>     ::=   <var> := <expr> <expr>        ::=   <expr> + <var> | <var> <var>          ::=   id

LIC. MARTHA MARTINEZ MORENO79

Page 80: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

 $ IF THEN :

=+ id

$ 1 1IF

IF <expr> THEN

<var> :=<expr> +

idTabla parcialmente llena para la gramatica dada.

  El analizador sintáctico utiliza la típica pila S y la variable para el

símbolo entrante R. Aunque la estructura de la pila tiene una pequeña diferencia; en lugar de sólo símbolos, se permitirá que cadenas de símbolos aparezcan en la pila. Las cadenas que aparezcan aquí serán cabezas (las cuales terminan en un símbolo terminal) de las partes derechas de las reglas. Por ejemplo, si la pila convencional en algún momento contiene:

 $ IF <expr> THEN  IF <expr> THEN <var>:=

 La pila podría verse como:

<var>:= IF <expr> THEN IF <expr> THEN $

 Note que todo lo que se hace es mantener juntos todos esos

símbolos; los cuales sabemos, deberán ser reducidos al mismo tiempo. También se utilizará una variable U, ésta estará vacía o contendrá el símbolo el cual la última frase ha sido reducida. Así, si la cadena parcial analizada hasta el momento es:

            $ IF <expr> THEN  IF <expr> THEN <var>:= <expr>

Entonces la pila se vería como arriba y <expr> estaría en U.

El analizador sintáctico usa la matriz como sigue. En cada paso el elemento en la cima de la pila corresponde a algún renglón de la matriz, debido a que ambas representan la cabeza (terminando en un símbolo

LIC. MARTHA MARTINEZ MORENO80

Page 81: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

terminal) de alguna parte derecha. El símbolo terminal entrante determina una columna de la matriz. Estas dos juntas determinan un elemento de la matriz el cual es el número de subrutina a ejecutar. Esta subrutina ejecuta la reducción necesaria o mete en la pila a R y obtiene el siguiente símbolo de entrada.

Realización del Análisis de Matriz de Transición.

Para llevar a cabo el análisis sintáctico y al mismo tiempo construir la matriz, se hará uso de una pila S que en cuestión a su estructura es algo diferente a las ya conocidas, ya que en estas podemos almacenar no únicamente símbolos sino también cadenas de símbolos, éstas cadenas serán las cabezas que definimos como renglones de la matriz y también se hace uso de una variable (R) que especificará el símbolo o los símbolos que puedan entrar a la pila.

 Ejemplo: Podemos tener en X momento esta expresión dentro de

la pila:             $ IF <expr> THEN  IF <expr> THEN <var>:=  La pila podría verse como:

<var>:= IF <expr> THEN IF <expr> THEN $

 También se hace uso de una variable U, la cual estará vacía o

contendrá el símbolo de la última frase reducida.En cada paso el elemento que se encuentre en la cima de la pila

corresponderá a algún renglón de la matriz, el símbolo terminal R determinará la columna de la matriz y los elementos pertenecientes a la matriz serán los números de subrutinas a ejecutar, las subrutinas tendrán la tarea de meter a R en la pila o de llevar a cabo una reducción.

Ejemplo:

Cuando iniciamos el análisis $ esta en la pila y la variable U está vacía, de acuerdo a la gramática que usamos en este ejemplo, el símbolo entrante debe ser IF e id debido a que estas inician partes

LIC. MARTHA MARTINEZ MORENO81

Page 82: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

derechas, por lo que colocaremos el número 1 (que corresponde a la primera subrutina) en el renglón $ que es el elemento en la cima de la pila y en las columnas IF e id, después realizaremos la subrutina.

 

$

U =R =

 $ IF THEN :

=+ id

$ 1 1IF

IF <expr> THEN

<var> :=<expr> +

id Subrutina 1: IF U <> " " then ERROR;

                   i :=  i + 1 ;                  S(i) = R ;                  SIGCOMPLEX.

La función SIGCOMPLEX coloca el siguiente símbolo de la cadena de entrada en R.

 

idIF$

U =R =

Ahora haremos un recorrido en la matriz para ver o indicar en que otras posiciones en la matriz se puede realizar la subrutina 1, para esto se debe de analizar que posible símbolo terminal de la columna podría seguirle al elemento terminal del renglón.

En el renglón IF basándonos en la regla 2, <expr> le sigue al símbolo IF y como vemos en la regla 4 <expr>:= <var> y en la regla 5 <var>:= id ; por lo tanto id puede ser un símbolo que le siga a IF por lo tanto colocamos en número 1 en la fila IF columna id.

En el renglón IF <expr> THEN, basándonos en la regla 2, <estado> le sigue al símbolo THEN y como vemos en la misma regla 2

LIC. MARTHA MARTINEZ MORENO82

Page 83: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

<estado>:= IF <expr> THEN <estado>; por lo tanto IF puede ser un símbolo que le sigue  a THEN y colocamos también el número 1 de la subrutina en la fila IF <expr> THEN columna IF; también vemos en la regla 3 que <estado>:= <var>:= <expr> y en la regla 5 <var>:= id por lo tanto también id puede ser un símbolo que le siga a THEN y también colocamos el número 1 en la misma fila pero en la columna id.

Valiéndose de estas reglas la matriz al terminar este paso queda: 

$ IF THEN :=

+ id

$ 1 1IF 1

IF <expr> THEN

1 1

<var> := 1<expr> + 1

id como vemos ahora en la cima de la pila se encuentra el símbolo id,

por lo que iremos al renglón id de la matriz y analizaremos que símbolo (de las columnas) puede ser válido en R, es decir, que símbolo podría seguirle a id y vemos que los símbolos $, THEN, := ó + son válidos, esto es porque se puede tener una expresión como la siguiente:

IF  id  THEN  id:=  id  +  id  $ por lo tanto la matriz queda: 

$ IF THEN :=

+ id

$ 1 1IF 1

IF <expr> THEN

1 1

<var> := 1<expr> + 1

id 2 2 2 2 2

Subrutina 2: IF U <> " " then ERROR;                   i: =  i - 1;                  U = '<var>'.                  

Aquí se hace una reducción y la pila queda:

LIC. MARTHA MARTINEZ MORENO83

Page 84: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

IF$

U = ' <var> 'R =

 Ahora en la cima de la pila se encuentra el símbolo IF por lo que

iremos al renglón IF de la matriz y analizaremos que símbolo (de las columnas) puede ser válido en R, es decir que símbolo podría seguir a IF pero tomando e cuenta que tenemos a <var> en U. Vemos que en el renglón IF la única que satisface tal condición es el símbolo + , así como también en el renglón <var>:=.

Por lo tanto la matriz quedará:$ IF THEN :

=+ id

$ 1 1IF 3 1

IF <expr> THEN

1 1

<var> := 3 1<expr> + 1

id 2 2 2 2 2

Subrutina 3: IF U <> '<expr>' and U <> '<var>'then ERROR;                   i :=  i + 1 ;                  S(i) = '<expr>'+                  SIGCOMPLEX.

   

'<expr>+'IF$

U = ' <var> 'R =

Ahora nos posicionamos en la fila <expr>+ de la matriz y analizaremos que símbolo (de las columnas) puede ser válido en R, es decir que símbolo podría seguir a <expr>+ tomando en cuenta también a <var> que esta en U y vemos que los símbolos $, THEN ó + son válidos, porque se puede tener una expresión como la siguiente:

IF <expr>+<var> THEN <var>:=  <expr>+<var>+<var> $                                                                                                                         <expr>     +<var>Por lo tanto la matriz quedará: 

LIC. MARTHA MARTINEZ MORENO84

Page 85: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

$ IF THEN :=

+ id

$ 1 1IF 3 1

IF <expr> THEN

1 1

<var> := 3 1<expr> + 4 4 4 1

id 2 2 2 2 2Subrutina 4: IF U <> "<var>" then ERROR;                   i :=  i - 1 ;                  U = '<expr>'

 '<expr>+'

IF$

U = ' <var> ' := '<expr>'

R =

Se hizo aquí otra reducción.Se siguen los mismos procedimientos, aunque cabe aclarar que en

la subrutina 5, se hace una parada.

Subrutina 5: IF U <> '<prog>' and U <> '<estado>' then ERROR;                   STOP         de tal forma que así quedaría la tabla:

$ IF THEN :=

+ id

$ 5 1 1IF 3 1

IF <expr> THEN

1 1

<var> := 3 1<expr> + 4 4 4 1

id 2 2 2 2 2  La matriz completa y las subrutinas para la gramática de ejemplo

se muestran a continuación:  

$ IF THEN :=

+ id

$ 5 1 0 6 0 1IF 0 0 9 0 3 1

IF <expr> THEN

7 1 0 6 0 1

LIC. MARTHA MARTINEZ MORENO85

Page 86: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

<var> := 8 0 0 0 3 1<expr> + 4 0 4 0 4 1

id 2 0 2 2 2 0 

6: if U <> '<var> then ERROR; U := '';

i := i + 1; S (i) = '<var> :=',

SIGCOMPLEX.

7: if U<> '<estado>' then ERROR; i := i - 1; 

U :='<estado>'.

8: if U <> '<var>' and    U <> '<expr>' then ERROR;

i := i - 1; U:= '<estado>';

9: if U <> '<var>' and    U <> '<expr>' then ERROR;

S (i) := 'IF <expr> THEN'; U := '';

SIGCOMPLEX.

0: ERROR; STOP.

LIC. MARTHA MARTINEZ MORENO86

Page 87: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

EJEMPLO DE ANÁLISIS LÉXICO DEL LENGUAJE DTONE

Este es el código en Java del Analizador Léxico.

Options { LOOKAHEAD = 3; IGNORE_CASE = true; STATIC = false; }

PARSER_BEGIN(dtone3)import java.io.*;import java.lang.*;import java.util.*;import javax.swing.*;

public class dtone3{ public FileOutputStream Sint1; public DataOutputStream Sint2;

public FileOutputStream Token1;public DataOutputStream Token2;

public String ar[]=new String[1024];

public void archSint_crear(String nombre_arch) { try { Sint1 = new FileOutputStream(nombre_arch); Sint2 = new DataOutputStream(Sint1); } catch (IOException Ex) { System.err.println("No se creo el archivo correctamente"); } }

public void archSint_cerrar() { try { Sint1.close(); } catch (IOException Ex) { System.err.println("No se cerro corectamente"); } }

public void archSint_escribe(String Mensaje) { try { Sint2.writeBytes(Mensaje+"\r"); Sint1.write('\n'); } catch(IOException Ex) {

System.err.println("No se escribio correctamente"); }

LIC. MARTHA MARTINEZ MORENO87

Page 88: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

}

public void archtoken_crear(String nombre_arch) { try { Token1 = new FileOutputStream(nombre_arch); Token2 = new DataOutputStream(Token1); } catch (IOException Ex) { System.err.println("No se creo el archivo correctamente"); } }

public void archtoken_cerrar() { try { Token1.close(); } catch (IOException Ex) { System.err.println("No se cerro corectamente"); } }

public void archtoken_escribe(String Mensaje) { try { Token2.writeBytes(Mensaje+"\r"); Token1.write('\n'); } catch(IOException Ex) {

System.err.println("No se escribio correctamente"); } }

public static void main(String args[]) throws ParseException{

dtone3 analiza;if(args.length == 0){

System.err.println("Uso del programa: \"java dtone3 archivo.ejm\"");}

else {

if(args.length == 1) {

try { analiza = new dtone3 (new java.io.FileInputStream(args[0])); analiza.archSint_crear("Sint.txt"); analiza.dtone(); analiza.archSint_cerrar();

} catch (java.io.FileNotFoundException e)

LIC. MARTHA MARTINEZ MORENO88

Page 89: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

{System.out.println("No se pudo leer el archivo : " + args[0] + " ");

}try

{ analiza = new dtone3 (new java.io.FileInputStream(args[0])); analiza.archtoken_crear("Lex.txt");

analiza.input(analiza);}

catch (java.io.FileNotFoundException e) {

System.out.println("No se pudo leer el archivo : " + args[0] + " ");}

} else { System.out.println("Formato: java dtone3 nombrearchivo.ejm");

}}

}

void EscribeSintactico(String mensaje){ archSint_escribe(mensaje);}

}PARSER_END(dtone3)

SKIP :{ " "| "\t"| "\n"| "\r"| "\f"}

TOKEN : { <paccess: ("a")("c")("c")("e")("s")("s")> | <pbool: ("b")("o")("o")("l")>| <pbreak: ("b")("r")("e")("a")("k")>| <pbyte: ("b")("y")("t")("e")>| <pcase: ("c")("a")("s")("e")>| <pclass: ("c")("l")("a")("s")("s")>| <pcontinue: ("c")("o")("n")("t")("i")("n")("u")("e")>| <pdefault: ("d")("e")("f")("a")("u")("l")("t")>| <pdelete: ("d")("e")("l")("e")("t")("e")>| <pdo: ("d")("o")>| <pelse: ("e")("l")("s")("e")>| <pexport: ("e")("x")("p")("o")("r")("t")>| <pfalse: ("f")("a")("l")("s")("e")>| <pfor: ("f")("o")("r")>| <pif: ("i")("f")>| <pint8: ("i")("n")("t")("8")>

LIC. MARTHA MARTINEZ MORENO89

Page 90: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

| <pint16: ("i")("n")("t")("1")("6")>| <pint32: ("i")("n")("t")("3")("2")>| <pmodule: ("m")("o")("d")("u")("l")("e")>| <pnew: ("n")("e")("w")>| <pprivate: ("p")("r")("i")("v")("a")("t")("e")>| <preturn: ("r")("e")("t")("u")("r")("n")>| <pswitch: ("s")("w")("i")("t")("c")("h")>| <ptoken: ("t")("o")("k")("e")("n")>| <ptrue: ("t")("r")("u")("e")>| <puint8: ("u")("i")("n")("t")("8")>| <puint16: ("u")("i")("n")("t")("1")("6")>| <puint32: ("u")("i")("n")("t")("3")("2")>| <puses: ("u")("s")("e")("s")>| <pwhile: ("w")("h")("i")("l")("e")>

}

TOKEN :{ <parenizq: "(">| <parender: ")">| <llaveizq: "{">| <llaveder: "}">| <corizq: "[">| <corder: "]">| <puncoma: ";">| <dospuntos: ":">| <coma: ",">| <punto: ".">| <comillas: "\"">}

TOKEN :{ <asigna: "=">| <mayor: ">">| <menor: "<">| <menigual: "<=">| <mayigual: ">=">| <suma: "+">| <resta: "-">| <div: "/">| <cambioizq: "<<">| <cambioder: ">>">| <producto: "*">| <residuo: "%">| <inc: "++">| <dec: "--">| <nobin: "~">| <nolog: "!">| <igual: "==">| <dif: "!=">| <ybin: "&">| <obin: "|">| <xor: "~|">| <ylog: "&&">

LIC. MARTHA MARTINEZ MORENO90

Page 91: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

| <olog: "||">| <sumasigna: "+=">| <restasigna: "-=">| <prodasigna: "*=">| <divasigna: "/=">| <resasigna: "%=">| <yasigna: "&=">| <oasigna: "|=">| <xorasigna: "~|=">| <camizqasig: "<<=">| <camderasig: ">>=">

}

TOKEN :{ <dig: ["0"-"9"] >| <letra: ["a" - "z","A" - "Z"] >| <entero: (<dig>)+ >| <real: ("-")? <entero> (".") <entero> >| <ident: <letra> (<letra> | <dig> | "_")* >| <cadena: <comillas> (~[ "'" ])* <comillas> >| <c_noval: ("ñ" |"^" | "¨" | "´" | "°" | "@" | "$") > | <cad_noval:(<letra> | <dig> | "_")* (<c_noval>)+ (<letra> | <dig> | "_" | <c_noval>)* >

}

void input(dtone3 analiza) :{ String mensaje;}{ ( mensaje=Checa()

{ System.out.println(mensaje); analiza.archtoken_escribe(mensaje); }

)+ <EOF>}

String Checa() : { Token t; }{

t=<paccess>{ return ( t.image +"\t\t Palabra reservada access"); }

| t=<pbool>{ return ( t.image +"\t\t Palabra reservada bool");}

| t=<pbreak>{ return ( t.image +"\t\t Palabra reservada break");}

| t=<pbyte>

LIC. MARTHA MARTINEZ MORENO91

Page 92: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

{ return ( t.image +"\t\t Palabra reservada byte");}

| t=<pcase>{ return ( t.image +"\t\t Palabra reservada case");}

| t=<pclass>{ return ( t.image +"\t\t Palabra reservada class");}

| t=<pcontinue>{ return ( t.image +"\t\t Palabra reservada continue");}

| t=<pdefault>{ return ( t.image +"\t\t Palabra reservada default");}

| t=<pdelete>{ return ( t.image +"\t\t Palabra reservada delete");}

| t=<pdo>{ return ( t.image +"\t\t Palabra reservada do");}

| t=<pelse>{ return ( t.image +"\t\t Palabra reservada else");}

| t=<pexport>{ return ( t.image +"\t\t Palabra reservada export");}

| t=<pfalse>{ return ( t.image +"\t\t Palabra reservada false");}

| t=<pfor>{ return ( t.image +"\t\t Palabra reservada for");}

| t=<pif>{ return ( t.image +"\t\t Palabra reservada if");}

| t=<pint8>{ return ( t.image +"\t\t Palabra reservada int8");}

| t=<pint16>{ return ( t.image +"\t\t Palabra reservada int16");}

| t=<pint32>{ return ( t.image +"\t\t Palabra reservada int32");}

| t=<pmodule>{ return ( t.image +"\t\t Palabra reservada module");}

| t=<pnew>{ return ( t.image +"\t\t Palabra reservada new");}

| t=<pprivate>{ return ( t.image +"\t\t Palabra reservada private");}

| t=<preturn>{ return ( t.image +"\t\t Palabra reservada return");}

LIC. MARTHA MARTINEZ MORENO92

Page 93: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

| t=<pswitch>{ return ( t.image +"\t\t Palabra reservada switch");}

| t=<ptoken>{ return ( t.image +"\t\t Palabra reservada token");}

| t=<ptrue>{ return ( t.image +"\t\t Palabra reservada true");}

| t=<puint8>{ return ( t.image +"\t\t Palabra reservada uint8");}

| t=<puint16>{ return ( t.image +"\t\t Palabra reservada uint16");}

| t=<puint32>{ return ( t.image +"\t\t Palabra reservada uint32");}

| t=<puses>{ return ( t.image +"\t\t Palabra reservada uses");}

| t=<pwhile>{ return ( t.image +"\t\t Palabra reservada while");}

| t=<puncoma>{ return ( t.image +"\t\t Simbolo ;");}

| t=<dospuntos>{ return ( t.image +"\t\t Simbolo :");}

| t=<coma>{ return ( t.image +"\t\t Simbolo ,");}

| t=<punto>{ return ( t.image +"\t\t Simbolo .");}

| t=<parenizq>{ return ( t.image +"\t\t Operador (");}

| t=<parender>{ return ( t.image +"\t\t Operador )");}

| t=<corizq>{ return ( t.image +"\t\t Operador [");}

| t=<corder>{ return ( t.image +"\t\t Operador ]");}

| t=<llaveizq>{ return ( t.image +"\t\t Operador {");}

| t=<llaveder>{ return ( t.image +"\t\t Operador }");}

| t=<asigna>{ return ( t.image +"\t\t Operador = ");}

LIC. MARTHA MARTINEZ MORENO93

Page 94: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

| t=<mayor>{ return ( t.image +"\t\t Operador > ");}

| t=<menor>{ return ( t.image +"\t\t Operador < ");}

| t=<menigual>{ return ( t.image +"\t\t Operador <= ");}

| t=<mayigual>{ return ( t.image +"\t\t Operador >= ");}

| t=<suma>{ return ( t.image +"\t\t Operador + ");}

| t=<resta>{ return ( t.image +"\t\t Operador - ");}

| t=<div>{ return ( t.image +"\t\t Operador / ");}

| t=<producto>{ return ( t.image +"\t\t Operador * ");}

| t=<residuo>{ return ( t.image +"\t\t Operador % ");}

| t=<cambioizq>{ return ( t.image +"\t\t Operador << ");}

| t=<cambioder>{ return ( t.image +"\t\t Operador >> ");}

| t=<inc>{ return ( t.image +"\t\t Operador ++ ");}

| t=<dec>{ return ( t.image +"\t\t Operador -- ");}

| t=<nobin>{ return ( t.image +"\t\t Operador ~ ");}

| t=<nolog>{ return ( t.image +"\t\t Operador ! ");}

| t=<igual>{ return ( t.image +"\t\t Operador == ");}

| t=<dif>{ return ( t.image +"\t\t Operador != ");}

| t=<ybin>{ return ( t.image +"\t\t Operador binario & ");}

| t=<obin>

LIC. MARTHA MARTINEZ MORENO94

Page 95: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

{ return ( t.image +"\t\t Operador binario | ");}

| t=<xor>{ return ( t.image +"\t\t Operador binario ~| ");}

| t=<ylog>{ return ( t.image +"\t\t Operador logico && ");}

| t=<olog>{ return ( t.image +"\t\t Operador logico || ");}

| t=<sumasigna>{ return ( t.image +"\t\t Operador asignacion += ");}

| t=<restasigna>{ return ( t.image +"\t\t Operador asignacion -= ");}

| t=<prodasigna>{ return ( t.image +"\t\t Operador asignacion *= ");}

| t=<divasigna>{ return ( t.image +"\t\t Operador asignacion /= ");}

| t=<resasigna>{ return ( t.image +"\t\t Operador asignacion %= ");}

| t=<yasigna>{ return ( t.image +"\t\t Operador asignacion &= ");}

| t=<oasigna>{ return ( t.image +"\t\t Operador asignacion |= ");}

| t=<xorasigna>{ return ( t.image +"\t\t Operador asignacion ~|= ");}

| t=<camizqasig>{ return ( t.image +"\t\t Operador asignacion <<= ");}

| t=<camderasig>{ return ( t.image +"\t\t Operador asignacion >>= ");}

| t=<dig>{ return ( t.image +"\t\t Digito ");}

| t=<ident> { return ( t.image +"\t\t Identificador ");}

| t=<letra>{ return ( t.image +"\t\t Letra ");}

| t=<entero>{ return ( t.image +"\t\t Numero Entero ");}

| t=<real>{ return ( t.image +"\t\t Numero real ");}

LIC. MARTHA MARTINEZ MORENO95

Page 96: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

| t=<c_noval>{ return ( t.image +"\t\t Caracter no valido ");}

| t=<cad_noval>{ return ( t.image +"\t\t Cadena no valida ");}

}

EJEMPLO DE ANALISIS SINTÁCTICO DEL LENGUAJE DTONE

Este es el código en Java del Analizador Sintáctico. Se describen las gramáticas y se da una breve descripción de cada una de ellas.

Esta gramática corresponde a la estructura general de todo el programa.

void dtone() : {}{try{CABECERA() CUERPO()}catch(ParseException e){System.out.println("Error en el programa");}}

Esta es la gramática que corresponde a la cabecera del programa. Éste tiene que ser el primer comando en todos los archivos. Identifica el archivo como código del dtone.

void CABECERA() : {}{try {VERSION() LIBRERIAS() }catch(ParseException e) {System.out.println("Error en la cabecera");}}

Esta es la gramática que corresponde a la versión que va en la cabecera. La versión es muy útil para identificar la versión del compilador que este código necesita.

void VERSION() : {}{try {<pdtone> <dig> <punto> <dig> <punto> <dig>}catch(ParseException e) {System.out.println("Error en la version");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

LIC. MARTHA MARTINEZ MORENO96

Page 97: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Esta es la gramática que corresponde a las librerías, lo que se hace es que se listan las librerías que van a ser llamadas para la ejecución del programa.

void LIBRERIAS() : {}{try {(<puses> <comillas> <ident>("/" <ident>)* "/*" <comillas> <puncoma>)+}catch(ParseException e) {System.out.println("Error en las librerias");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);}}

Esta es la gramática en la cual se define el cuerpo del programa, en el se van a listar las instrucciones para del programa.

void CUERPO() : {}{try {<pmodule> <ident> <llaveizq> DECLARA() (INSTRUC())+ <llaveder>}catch(ParseException e) {System.out.println("Error en el cuerpo");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta es la gramática para las declaraciones de variables que se van a utilizar en el programa.

void DECLARA() : {}{try {(D_VAR() | D_ARR() | D_CONST() | D_FUNCION() )*}catch(ParseException e) {System.out.println("Error en las declaraciones");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta es la gramática de la declaración de variables.

void D_VAR() : {}{try {(DEC_VAR() | DECINI_VAR()) <puncoma>}catch(ParseException e) {

LIC. MARTHA MARTINEZ MORENO97

Page 98: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

System.out.println("Error en las declaracion de variable");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta es la gramática de la declaración de variables.

void DEC_VAR() : {}{try {TIPO() <ident> }catch(ParseException e) {System.out.println("Error en las declaracion de variable");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta gramática corrsponde al tipo de datos con el cual se va a declarar la variable.

void TIPO() : {}{(<pint8> | <pint16> | <pint32> | <puint8> | <puint16> | <puint32> | <pbool> | <pbyte>)}

Esta gramática corresponde a la declaración e inicialización de variables.

void DECINI_VAR() : {}{try {DEC_VAR() <igual> (<entero> | <real> | <cadena>) <puncoma> }catch(ParseException e) {System.out.println("Error en las declaracion de variable");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta gramática corresponde a la declaración de un arreglo.

void D_ARR() : {}{try {TIPO() <ident> <corizq> (<entero>)* <corder> <puncoma> }catch(ParseException e) {System.out.println("Error en las declaracion de arreglo");

LIC. MARTHA MARTINEZ MORENO98

Page 99: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta gramática corresponde a la declaración de una constante.

void D_CONST() : {}{try {<pdefine> <ident> <igual> (<entero>|<real>|<cadena>) <puncoma> }catch(ParseException e) {System.out.println("Error en las declaracion de constante");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta gramática corresponde a la declaración de una función.

void D_FUNCION() : {}{try {TIPO() <ident> <parenizq> PARAM() <parender> <llaveizq> DECLARA() (INSTRUC())+ < llaveder >}catch(ParseException e) {System.out.println("Error en la declaracion de la funcion");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta gramática corresponde a la declaración de parámetros del programa.

void PARAM() : {}{try {(DEC_VAR() | D_ARR()) (<coma> (DEC_VAR() | D_ARR() ) )*}catch(ParseException e) {System.out.println("Error en los parametros");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

LIC. MARTHA MARTINEZ MORENO99

Page 100: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Esta gramática define las instrucciones que serán dadas para que el programa finalmente cumpla con las ordenes que se le están dando a través de dichas instrucciones.

void INSTRUC() : {}{try {(ASIGNACION() | CICLO() | LLAM_FUNCION())}catch(ParseException e) {System.out.println("Error en la instruccion");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta es la gramática que define las asignaciones que podemos hacer en el lenguaje de programación Dtone.

void ASIGNACION() : {}{try {IZQ() <asigna> DER() <puncoma>}catch(ParseException e) {System.out.println("Error en la asignacion");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta es la gramática que define la parte izquierda de las asignaciones que podemos hacer en el lenguaje de programación Dtone.

void IZQ() : {}{try {(<ident> | ARREGLO())}catch(ParseException e) {System.out.println("Error en la parte izquierda de la asginacion");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta gramática define al arreglo que puede ir en la parte izquierda de la asignación, es decir al arreglo se le va a asignar un valor.

void ARREGLO() : {}{try {<ident> <corizq> (<ident>|<entero>) (<coma> (<ident>|<entero>))* <corder>

LIC. MARTHA MARTINEZ MORENO100

Page 101: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

}catch(ParseException e) {System.out.println("Error en el arreglo");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta es la gramática que define la parte derecha de las asignaciones que podemos hacer en el lenguaje de programación Dtone.

void DER() : {}{try {(IZQ() | <cadena> | OPER_ARIT() | <entero> | <real>)}catch(ParseException e) {System.out.println("Error en la parte derecha de la asignacion");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta gramática corresponde a los operadores aritméticos que pueden ir en la parte derecha de una asignación.

void OPER_ARIT() : {}{try {(<ident> | <entero> | <real> | ARREGLO()) ARITOP() (<ident> | <entero> | <real> | ARREGLO())}catch(ParseException e) {System.out.println("Error en la operacion");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta gramática define el simbolo del operador aritmético.

void ARITOP() : {}{ (<suma> | <resta> | <div> | <producto> | <residuo>)

}

Esta gramática define los ciclos que puede contener el cuerpo del programa en el lenguaje Dtone.

void CICLO() : {}

LIC. MARTHA MARTINEZ MORENO101

Page 102: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

{ (GIF() | GWHILE() | GDO() | GFOR() | GCASE())}

Esta es la gramática del IF. Se evalúa la condición, si produce una verdad, la verdadera trayectoria se ejecuta, si no la falsa trayectoria (opcional) se ejecuta.

void GIF() : {}{try {<pif> <parenizq> OPER_REL() <parender> (INSTRUC())+}catch(ParseException e) {System.out.println("Error en el IF");}}

Esta es la gramática de los operadores relacionales que pueden ir dentro de alguna instrucción de control de flujo.

void OPER_REL() : {}{try {(<ident> | <entero> | <real> | ARREGLO()) RELOP() (<ident> | <entero> | <real> | ARREGLO())}catch(ParseException e) {System.out.println("Error en la operacion");}}

Esta es la gramática de los símbolos de los operadores relacionales que pueden ir dentro de alguna instrucción de control de flujo.

void RELOP() : {}{ (<mayor> | <menor> | <menigual> | <mayigual> | <igual>)

}

Esta es la gramática del ciclo WHILE. En este ciclo se ejecuta el cuerpo si la condición es verdadera, en caso contrario no.

void GWHILE() : {}{try {<pwhile> <parenizq> OPER_REL() <parender> <llaveizq> (INSTRUC())+ <llaveder>}catch(ParseException e) {System.out.println("Error en el WHILE");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

LIC. MARTHA MARTINEZ MORENO102

Page 103: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Esta es la gramática del ciclo DO. En este ciclo, el cuerpo es ejecutado mientras que la condición sea cierta. El cuerpo del ciclo se ejecuta por lo menos una vez.

void GDO() : {}{try {<pdo> <llaveizq> (INSTRUC())+ <llaveder> <pwhile> <parenizq> OPER_REL() <parender> <puncoma>}catch(ParseException e) {System.out.println("Error en el DO");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta es la gramática del ciclo FOR. El cual tiene una estructura muy parecida a la de C.

void GFOR() : {}{try {<pfor> <parenizq> (DECINI_VAR() | ASIGNACION()) <puncoma> OPER_REL() <puncoma> ASIGNACION() <parender> <llaveizq> (INSTRUC())+ <llaveder>}catch(ParseException e) {System.out.println("Error en el FOR");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

Esta gramática corresponde al case. Esta estructura no requiere declaraciones de ruptura.

void GCASE() : {}{try {<pswitch> <parenizq> (INSTRUC())+ <parender> <llaveizq> (<pcase> <entero> <dospuntos> (INSTRUC())+)+ (<pdefault> <dospuntos> (INSTRUC())+)? <llaveder>}catch(ParseException e) {System.out.println("Error en el CASE");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

La siguiente gramática describe la llamada a una función en Dtone.

void LLAM_FUNCION() : {}{try

LIC. MARTHA MARTINEZ MORENO103

Page 104: Manual Completo de Programacion de Sistemas I

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

{<ident> <parenizq> (<ident> | <entero> | <cadena> | <real>)* <parender> <puncoma>}catch(ParseException e) {System.out.println("Error en la llamada");Token t;do{ t = getNextToken();} while(t.kind != puncoma & t.kind != EOF);

}}

LIC. MARTHA MARTINEZ MORENO104