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

PLAN

DE

TRABAJO

1

LIC. MARTHA MARTINEZ MORENO

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

1.

Datos Generales. Teórica/Práctica Horas de Clase por semana: 4 Hrs. Horas de clase por semestre: 60 Hrs. Carrera en que se imparte: Ing. en Sist. Comp. Semestre: Sexto

Materia:

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 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.

B.

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.

2

LIC. MARTHA MARTINEZ MORENO

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

• Intelectual. Que el alumno aprenda a preparar exposiciones profesionales y exponer sus ideas de manera clara y por escrito. • 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.

3

LIC. MARTHA MARTINEZ MORENO

basada en un conjunto de instrucciones en alto nivel. caracteres y reglas de uso que permiten a las personas "comunicarse" con las computadoras. EXPRESIONES REGULARES: Conjunto de símbolos que aceptan una palabra reservada. 4 LIC. Querys. Conjunto de herramientas que nos permiten crear software de base que son de utilidad para interactuar con la máquina. Los lenguajes son sistemas de comunicación. MARTHA MARTINEZ MORENO . Algunos se crean para una aplicación especial. cálculo/manipulación de textos. LENGUAJES DE PROGRAMACIÓN LENGUAJE DE PROGRAMACIÓN: Es la notación formal para la descripción de algoritmos. AUTÓMATA: Son las cadenas posibles que aceptan un lenguaje. Sistema Operativo. mientras que otros son herramientas de uso general más flexibles que son apropiadas para muchos tipos de aplicaciones. lógica/comparación y almacenamiento/recuperación. SOFTWARE DE Cargador. que finalmente pasarán a bajo nivel para interactuar con el hardware y generar herramientas de trabajo. GRAMÁTICA: Reglas para escribir las sentencias del lenguaje.MANUAL DE PROGRAMACIÓN DE SISTEMAS I UNIDAD I INTRODUCCIÓN CONCEPTOS PROGRAMACIÓN DE SISTEMAS: Conjunto de reglas para crear soluciones a problemas computables. En todo caso los lenguajes de programación deben tener instrucciones que pertenecen a las categorías ya familiares de entrada/salida. Existen por lo menos varios cientos de lenguajes y dialectos de programación diferentes. Un lenguaje de programación consiste en todos los símbolos. BASE: Compilador.

*. 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 computadoras tiene un código de 5 LIC. lenguajes ensambladores y lenguajes de alto nivel. caracteres y síntaxis de los lenguajes de máquina. aunque todos los lenguajes de programación tienen un conjunto de instrucciones que permiten realizar dichas operaciones.-./.MANUAL DE PROGRAMACIÓN DE SISTEMAS I EJEMPLO DE SÍMBOLOS QUE COMPONEN UN PROGRAMA : SIMBOLOS ESPECUALES FUNCIONES SIMBOLOS QUE RECHAZA EL LENGUAJ E IDENTIFICADORES ARITMETICOS LENGUAJ E PASCAL LOGICOS +.MOD AND NOT OR < > > < = = := DELIMITADORES OPERACIONALES RELACIONALES ASIGNACION PALABRAS RESERVADAS CICLOS DECLARACION DE VARIABLES DECLARACION DE CONSTANTES No obstante. que dice a la computadora cuál es la función que va a realizar. existe una marcada diferencia en los símbolos. La primera es el comando u operación. Todas las instrucciones preparadas en cualquier lenguaje de máquina tienen por lo menos dos partes. MARTHA MARTINEZ MORENO .

La codificación en lenguaje ensamblador es todavía un proceso lento. Además. Lenguaje ensamblador La comunicación en lenguaje de máquina es particular de cada procesador que se usa. pero los programas ensambladores traducen antes los símbolos de código de operación especificados a sus equivalentes en lenguaje de máquina. naturalmente. Ahorran tiempo y requieren menos atención a detalles .MANUAL DE PROGRAMACIÓN DE SISTEMAS I operación para cada una de sus funciones. se desarrollaron códigos mnemotécnicos para las operaciones y direcciones simbólicas. Es decir. y programar en este lenguaje es muy difícil y tedioso. Se incurren en menos errores y los que se cometen son más fáciles de localizar. Todas las computadoras actuales tienen códigos mnemotécnicos aunque. A principios de la década de 1950. el número de operandos de una instrucción varía en las distintas computadoras. MARTHA MARTINEZ MORENO . están diseñados para la marca y modelo específico de procesador que se utiliza. Además. y con el fin de facilitar la labor de los programadores. 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. 6 LIC. por lo que se empezó a buscar mejores medios de comunicación con ésta. los símbolos que se usan varían en las diferentes marcas y modelos. una desventaja importante de estos lenguajes es que tienen una orientación a la máquina. La computadora sigue utilizando el lenguaje de máquina para procesar los datos. Pero existen limitaciones. La segunda parte de la instrucción es el operando. que indica a la computadora donde hallar o almacenar los datos y otras instrucciones que se van a manipular. 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. que conforman un lenguaje mnemotécnico. los programas en lenguaje ensamblador son más fáciles de modificar que los programas en lenguaje de máquina. Los lenguajes ensambladores tienen ventajas sobre los lenguajes de máquina.

los programas en lenguaje de alto nivel se pueden utilizar con diferentes marcas de computadoras sin tener que hacer modificaciones considerables. Para agilizar la codificación. es importante reconocer que ya no es necesario que nos comuniquemos en este lenguaje de "unos" y "ceros". La razón fundamental de la 7 LIC. funcional. 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. una sola macroinstrucción podía producir varias líneas de código en lenguaje de máquina. a su vez. En 1950. Esto permite reducir sustancialmente el costo de la reprogramación cuando se adquiere equipo nuevo. al desarrollo de lenguajes de alto nivel que a menudo están orientados hacia una clase determinada de problemas de proceso. Los lenguajes de programación se dividen en 4 principales paradigmas: imperativo.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Aunque en la actualidad ya no se emplea. orientado a objetos y lógico. A diferencia de los programas de ensamble. 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. los lenguajes de programación imperativa pueden ser implementados eficientemente. MARTHA MARTINEZ MORENO . cuando los programadores reconocieron que las variables y comandos de asignación constituyen una simple pero útil abstracción de memoria que se actualiza. pero es el que internamente una computadora reconoce o "habla". Dicho de otra manera. El desarrollo de las técnicas mnemotécnicas y las macroinstrucciones condujo. Lenguajes imperativos Son llamados así porque están casados en comandos que actualizan variables que están en almacenamiento. Debido a su relación cerrada con las arquitecturas de las máquinas.

es decir. que se pueden considerar las principales estructuras de control en este tipo de lenguajes. MARTHA MARTINEZ MORENO . por tanto. Utilización de funciones polimórficas. ADA C CLIPPER & XBASE ENSAMBLADOR QUICKBASIC BASIC EUPHORIA FORTRAN PASCAL Lenguajes funcionales Los lenguajes funcionales se basan en el concepto de función. Las características fundamentales de estos lenguajes se exponen a continuación: Utilización de funciones sobre elementos de primer orden. 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.MANUAL DE PROGRAMACIÓN DE SISTEMAS I programación imperativa está relacionado a la naturaleza y propósito de la programación. Utilización de funciones de orden superior. es decir. aquéllas cuyo tipo devuelto depende del tipo de los argumentos. aquellas funciones que aceptan en su lista de argumentos otras funciones. 8 LIC. el objeto básico y fundamental que manejamos son las funciones.

estos pueden clasificarse en dos tipos: Puros: aquellos en los que sólo podemos aplicar funciones. Ejemplos de lenguajes funcionales: ML CAML Haskell Sheme LISP Lamda – Cálculo Iswim APL FP Hope Miranda Eden Gofer Erlang Programación Orientada a Objetos SmallTalk Programación Orientada a la lógica PROLOG 9 LIC. Existencia de un sistema de tipos fuerte Uso del Patter Matching: comprobación de patrones. Impuros: aquellos que poseen evaluación ansiosa. donde la evaluación de los argumentos se realiza antes de aplicar la función. Posee evaluación perezosa.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Evaluación perezosa (o lazy). las expresiones no se evalúan hasta que no se necesitan los resultados. es decir. Vistas las características fundamentales de los lenguajes funcionales. Estas son las únicas estructuras de control. MARTHA MARTINEZ MORENO . La evaluación perezosa nos proporciona la ventaja de trabajar con listas potencialmente infinitas. Es justamente. lo contrario de la evaluación eager(ansiosa).

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. En los lenguajes de bajo nivel los programas que permiten pasar de un programa fuente a un programa objeto se llaman programas 10 LIC. 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.MANUAL DE PROGRAMACIÓN DE SISTEMAS I 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. LOGO por su identificación con LSIP. Los tres estilos de programación son los siguientes: programación funcional. cae de lleno dentro de este estilo. TRADUCTORES Los traductores son programas que permiten pasar de un programa fuente a un programa objeto. MARTHA MARTINEZ MORENO . ALGOL ADA C COBOL PASCAL LENGUAJES DE INTELIGENCIA ARTIFICIAL Podemos distinguir tres grandes estilos o subfamilias de los lenguajes de inteligencia artificial. El lenguaje más representativo del estilo de programación por objetos es el SMALLTALK. programación relacional y programación por objetos. El lenguaje más representativo del estilo funcional es el LISP. pero existen varios dialectos de LISP que permite programar en esta forma. El lenguaje más representativo del estilo relacional PROLOG.

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

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 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

11

LIC. MARTHA MARTINEZ MORENO

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

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 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.

12

LIC. MARTHA MARTINEZ MORENO

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

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.

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.

13

LIC. MARTHA MARTINEZ MORENO

MARTHA MARTINEZ MORENO . si no que es una función. y verifica que correspondan a una secuencia lógica (identificador. su tipo. al igual que la tabla de símbolos no es una etapa del proceso de compilación. C++ (int suma. sino que una tarea. que por lo general consiste en un código maquina relocalizable o código ensamblador. análisis sintáctico. id1= id2+ id3 * 10 14 LIC. Esta secuencia de caracteres recibe el nombre componente léxico o lexema. palabra reservada etc. 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. La tabla de símbolos no es una etapa del proceso de compilación. Supongamos que un compilador tiene que analizar la siguiente preposición: Preposición: suma = var1 + var2 + 10. 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. una función que debe realizar el proceso de compilación. y así sucesivamente con todos los componentes léxicos que aparezcan. su ámbito y en el caso de los procedimientos el número de argumentos el tipo de los mismos etc. análisis semántico).) el analizador léxico agregaría un identificador en la tabla de símbolos. y sus atributos.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Etapa de generación de código. En otras palabras una tabla de símbolos es una estructura de datos. La tabla de símbolo es accedida tanto para escritura como parar lectura por todas las etapas. En ella se almacenan los identificadores que aparecen en el código fuente puro. se lleva el código intermedio final a código maquina o código objeto. Análisis Léxico El analizador léxico lee los caracteres del programa fuente. 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.). que contiene un registro por cada identificador. si la preposición hubiese sido la declaración del identificador "suma" en lenguajes C. Detector de errores o manejador de errores. como así también los atributos de los mismos. si no esta produce un error porque todavía no fue declarado. muy importante.

temp1= tipo_ent(10) temp2= id3 * temp1 temp3= id2 + tem2 id1= temp3 Esta etapa se lleva la preposición a como un programa para una 15 LIC. = / \ 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 | 10 Generador de código intermedio una representación intermedia maquina abstracta.MANUAL DE PROGRAMACIÓN DE SISTEMAS I 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. MARTHA MARTINEZ MORENO . que es representada en forma de un árbol sintáctico.

Al pasar por él modulo de preprocesado. El siguiente código esta definido de acuerdo al standard ANSI C. R1 ADDF R2. # 1 "hmundo. MARTHA MARTINEZ MORENO .MANUAL DE PROGRAMACIÓN DE SISTEMAS I Optimización de código El código intermedio obtenido es representado de una forma mas optima y eficiente.h> void main() { char* frase= " Hola Mundo. typedef struct { int _cnt. frase ). el código fuente queda de la siguiente manera. temp1= id3 * 10. R2 MULT #10.0.c" # 1 "c:/compilador/include/stdio. 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. char *_ptr. ).!!!".h" 1 3 # 12 " c:/compilador/include/stdio.. R2 MOVF id2. id1 Este el código objeto obtenido que es enviado al modulo de ensamblado.. printf("%s". }.h" 2 3 typedef void *va_list. R1 MOVF R1. typedef long unsigned int size_t. 16 LIC.h" 1 3 # 1 " c:/compilador/include/sys/types.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. #include<stdio.

. El código de salida enviado al modulo de enlazado es el siguiente. èÈÿÿÿƒÄ Éà ヘ v&#0. L&#0. int _bufsiz. @&#0. transformara el código fuente puro (expandido) en código ensamblador y lo envía al modulo de ensamblado. ghmundo.&#128.Œ&#0.þÿ&#0. . } FILE.!!!\0" LC1: Este código será analizado por él modulo de ensamblado. &#0.!!!&#0.&#0...@&#0...&#0. U‰åƒìèÝÿÿÿÇEü&#0.c" compiler_compiled...&#0.@&#0. int _flag. Este código es pasado al modulo de compilación quien luego de analizarlo y verificar si se encuentra correcto..: ___compiled_c: .h.³Ú(7ô&#0. que lo llevara a código binario no enlazado.&#0.Ì&#0. y que serán posiblemente utilizadas en el código fuente original..ヘ v&#0. .@&#0.ascii " Hola Mundo...bss&#0.. char *_name_to_remove.data&#0.. y lo enviara al modulo de enlazado.‹EüPh&#0. int _file...c&#0.file "hmundo....@&#0.file&#0. .text&#0. Hola Mundo. UNIDAD II ELEMENTOS DE LA PROGRAMACIÓN DE SISTEMAS CARGADORES 17 LIC. MARTHA MARTINEZ MORENO .@&#0.%s&#0..MANUAL DE PROGRAMACIÓN DE SISTEMAS I char *_base.. El nuevo código contiene el encabezado o prototipo de la/s función/es que se encuentran en el archivo de cabecera stdio.text LC0: .

etc. se dice que es un cargador absoluto. Entre los programas que se manejan en la programación de sistemas se encuentran: los sistemas operativos. 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. 18 LIC. ala acción de motor se le llama vulgarmente “Lindar”. el programa guardado en algún dispositivo de almacenamiento secundario. un mismo programa necesita ejecutarse en diferentes posiciones de memoria. la introducción debe realizarse de forma adecuada. • • 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. el programa cargador pone en memoria las instrucciones guardadas en sistemas externos. discos. es decir. • Cargadores ligadores: conocidos también como “Link Editor” por su término en ingles o simplemente “Linker”. podemos clasificar a los cargadores en: cargadores iniciales. si no referencias relativas a una dirección especial llamada Dirección de Reubicación. absolutos. Cargadores con publicación: en ocasiones. los manejadores de base de datos. Si dichas instrucciones se almacenan siempre en el mismo espacio de memoria (cada vez que se ejecute el programa cargador). Un cargador es un programa que coloca en la memoria para su ejecución. Cargadores Absolutos: como ya se menciono.) sirven para cargar con la memoria pequeños programas que inician el funcionamiento de la computadora.MANUAL DE PROGRAMACIÓN DE SISTEMAS I La programación de sistemas se refiere a la creación de programas cuya finalidad es servir a otros programas. sin emplear referencias absolutas a direcciones de memoria. MARTHA MARTINEZ MORENO . Dependiendo de la manera en que se manejan los procesos de ligas y carga. Para esto. los compiladores.

Generalmente dichas rutinas se encuentran guardadas en ficheros especiales llamados “Librerías”. Obteniendo en la traducción las rutinas externas a las que hace referencia dicho programa. MARTHA MARTINEZ MORENO . o el mismo programa ligador puede también realizar la tarea de carga. es posible guardar el resultado del proceso de liga en un archivo que podrá ser utilizado por un cargador. En el proceso de carga reubicable (relocalizable). Para esto. un mismo programa puede ejecutarse en diferentes posiciones de memoria. 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. antes de cargar un programa. donde están almacenadas todas las rutinas externas susceptibles a ser empleados por los diferentes programas del usuario. Esto último evita el tener que guardar el código ejecutable en un archivo. Allí va el programa ligador cuando esta realizando el montaje de un programa a buscarlas y las adjunta al programa objeto. 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. con lo que se ahorra espacio en disco. 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. 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.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Montar un programa consiste en añadir al programa objeto. 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. el programa objeto debe utilizar referencias a relativas a una dirección especial llamada dirección de reubicación. 19 LIC. Para esto. 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.

se escribieron programas para traducir automáticamente los programas escritos en lenguaje ensamblador a lenguaje máquina. escrito en lenguaje de máquina. La entrada para un ensamblador es un programa fuente escrito en lenguaje ensamblador. Al programa que traduce un programa objeto a partir de un programa escrito en lenguaje ensamblador lo llamaremos ensamblador 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 20 LIC. ENSAMBLADORES Que es ensamblador y para que sirve? Cuando se empezaron a utilizar símbolos nemotécnicos. A estos programas traductores se les llamo ensambladores. Para evitar confusiones. MARTHA MARTINEZ MORENO . 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. de aquí en adelante llamaremos lenguaje ensamblador al conjunto de nemotécnicos y a las reglas para su manejo. El programa objeto incluye también la información necesaria para que el cargador pueda preparar el programa objeto para su ejecución. La salida es un programa objeto.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Este enlace se llama estático porque se realiza antes de ejecutar el programa.

puede presentar problemas de espacio de memoria. Este tipo de ensamblador tiene la ventaja de que se puede comprobar inmediatamente el programa sin necesidad de transportarlo de un lugar a otro. podemos clasificarlos de acuerdo a características. 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. MARTHA MARTINEZ MORENO .MANUAL DE PROGRAMACIÓN DE SISTEMAS I Tipos de Ensambladores. El empleo de este tipo de traductores permite aprovechar el soporte de medios físicos (discos. 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. ya que el traductor ocupa espacio que no puede ser utilizado por el programador. • Ensambladores Residentes. La ventaja de estos ensambladores es que permiten ejecutar inmediatamente el programa. para su ejecución. etc.). Así podemos clasificarlos en: • Ensambladores Cruzados (Cross-Assembler). Sin embargo. impresoras. Son aquellos que permanecen en la memoria principal de la computadora y cargan. pantallas. la desventaja es que deben mantenerse en la memoria principal tanto el ensamblador como el programa fuente y el programa objeto. como se hacía en cross-assembler. también ocupará memoria el programa fuente y el programa objeto. Aunque todos los ensambladores realizan básicamente las mismas tareas. 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. 21 LIC. y sin necesidad de programas simuladores. Asimismo. al programa objeto producido.

dependiendo de las posibilidades de definición y manipulación de las macroinstrucciones. 22 LIC. etiquetas. El programa que indica al intérprete de instrucciones de la UCP cómo debe actuar se denomina microprograma. 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. • Microensambladores. se conozca la dirección de dicho símbolo y se pueda traducir de forma correcta. Generalmente. estos ensambladores obligan a definir los símbolos antes de ser empleados para que. para lo cual se utilizan microensambladores. Debido a su forma de traducción. pero normalmente son programas bastantes complejos. pero tiene el inconveniente indicado. El programa que ayuda a realizar este microprograma se llama microensamblador. MARTHA MARTINEZ MORENO . Existen procesadores que permiten la modificación de sus microprogramas. etc. Estos ensambladores son sencillos. por lo que suelen ser ensambladores residentes. Son ensambladores que permiten el uso de macroinstrucciones (macros). • Ensambladores de una fase. Debido a su potencia.MANUAL DE PROGRAMACIÓN DE SISTEMAS I • Macroensambladores. cuando aparezca una referencia a un determinado símbolo en una instrucción. los procesadores utilizados en las computadoras tienen un repertorio fijo de instrucciones. baratos y ocupan poco espacio. que el intérprete de las mismas interpretaba de igual forma un determinado código de operación. • Ensambladores de dos fases. También va construyendo la tabla de símbolos a medida que van apareciendo las definiciones de variables. Puede variar la complejidad de los mismos. es decir. normalmente son programas robustos que no permanecen en memoria una vez generado el programa objeto.

pueden realizarse saltos hacia adelante. leen el programa fuente y construyen una tabla de símbolos. se debe conocer la dirección de cada uno de los símbolos que intervienen en ella. en la segunda fase. se utiliza el proceso de ensamble de dos pasos o fases. En la primera fase. En la primera fase. MARTHA MARTINEZ MORENO . no pueden quedar referencias pendientes porque ya no habrá otra oportunidad de resolverlas. 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. Esto debido a que. En la segunda fase. Como se vio en la sección anterior. todos los símbolos deben estar definidos antes de emplearse. vuelven a leer el programa fuente y pueden ir traduciendo totalmente. o se ejecuta si es una seudoinstrucción. 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. Estos ensambladores son los más utilizados en la actualidad. Para que el ensamble de un paso funciones. La tabla de símbolos se va construyendo a medida que se avanza en la lectura de las líneas del programa fuente. El proceso de ensamble de uno. para traducir correctamente cada instrucción. Para resolver el problema que presenta el proceso de ensamble de un paso. 23 LIC. 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. dos o más pasos. Como ya se conocen las direcciones de las etiquetas utilizadas. existen ensambladores que realizan su tarea en una o más fases o pasos.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Los ensambladores de dos fases se denominan así debido a que realizan la traducción en dos etapas. se lee el programa fuente y se construye la tabla de símbolos. de esta manera. puesto que conocen la totalidad de los símbolos utilizados y las posiciones que se les ha asignado. En otras palabras.

Para utilizar una macro. Las expresiones son combinaciones de literales y operadores. 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. Cada traductor dará sus reglas de construcción de expresiones y. MACROPROCESADORES USOS DE UN MACROPROCESADOR Con el fin de evitar al programador la tediosa repetición de partes idénticas de un programa. En los lenguajes de alto nivel. la evaluación de las expresiones se hace en tiempo de ejecución. muy importante. de cómo las evalúa.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Literales y Expresiones. las literales son mecanismos mediante los cuales se reservan espacios de memoria para guardar valores de cierto tipo. no durante la ejecución. En lenguaje ensamblador las expresiones involucran valores constantes y operadores. 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. 24 LIC. Los resultados se almacenan como constantes ya que los cálculos ocurren durante el ensamble. En computació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. primero hay que declararla. 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. MARTHA MARTINEZ MORENO . No debe confundirse el manejo de expresiones en lenguaje ensamblador con el manejo de expresiones en los lenguajes de alto nivel. En la declaración se establece el nombre que se le dará a la macro y el conjunto de instrucciones que representará. Generalmente.

La utilización de macros posibilita la reducción del tamaño del código fuente. de acuerdo a las tareas que realizan. aunque el código objeto tiende a ser mayor que cuando se utilizan funciones. 25 LIC. La mayoría de los ensambladores y compiladores permiten el uso de pseudoinstrucciones condicionales del tipo IF . 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. se incluye en el programa fuente el archivo de la biblioteca de macros correspondiente. en una primera pasada. y almacenarlas en archivos que se constituyen en bibliotecas de macros. el macroprocesador hará la sustitución por las instrucciones correspondientes. ELSE.. pero la utilización o invocación a la macro (macrollamada) puede hacerse cuantas veces sea necesario. la tabla de macrodefiniciones contiene las definiciones de todas las macros a utilizar en el programa. por medio de las cuales se puede controlar la traducción de ciertos bloques de programa.. cuando se requiera la utilización de alguna macro en particular. El macroprocesador se encarga. de registrar todas las declaraciones de macros y de rastrear el programa fuente para detectar todas las macrollamadas. A este proceso de sustitución se le denomina expansión de la macro.MANUAL DE PROGRAMACIÓN DE SISTEMAS I La declaración se realiza una sola vez. Como su nombre lo indica. En cada lugar donde encuentre una macrollamada. Es tan común el empleo de macroinstrucciones se les considera como una extensión de los lenguajes. MARTHA MARTINEZ MORENO . De esta manera. En ocasiones es conveniente agrupar macros. De manera similar se considera al procesador de macroinstrucciones o macroprocesador como una extensión del ensamblador o compilador utilizado.

permitir que los usuarios compartan entre sí el hardware y los datos. y el objetivo secundario es que el hardware del computador se emplee de manera eficiente. Los programas de aplicación (compiladores. es posible que haya diferentes programas de aplicación. Los sistemas operativos son ante todo administradores de recursos. el principal recurso que administran es el hardware del computador: los procesadores. los dispositivos de comunicación y los datos. el cual cuenta con y componentes básicos: hardware. El sistema operativo controla y coordina el uso del hardware entre los diversos programas de aplicación de los distintos usuarios. máquinas. por consiguiente.MANUAL DE PROGRAMACIÓN DE SISTEMAS I 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. programas de aplicación y los usuarios. otros computadores) que intentan resolver problemas diferentes. El hardware (unidad central de proceso (UCP). lograr que el sistema de computación se use de manera cómoda. Puede haber distintos usuarios (personas. memoria y dispositivos de entrada y salida (E/S)) proporciona los recursos de computación básica básicos. de tal manera que su uso sea lo mas sencillo y eficiente. los medios de almacenamiento. Los sistemas operativos realizan muchas funciones. MARTHA MARTINEZ MORENO . los dispositivos de entrada/salida. 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. evitar que los usuarios 26 LIC. Sistemas Operativos. entonces. sistemas de bases de datos. Un sistema Operativo es parte importante de casi cualquier sistema de cómputo. para esto debe administrar los recursos del sistema. como proporcionar la interfaz con el usuario. El objetivo principal de un sistema operativo es. El principal objetivo de un sistema operativo es proporcionar una interfaz entre el equipo y los programas.

Tipos de Sistemas Operativos. los requisitos de memoria y las características incluidas varían en forma considerable de un sistema a otro. Este propósito tiene una importancia especial en los grandes sistemas multiusuario compartidos. los cuales pueden atender solo un trabajo (y por lo tanto un solo usuario) a la vez. por lo que es deseable hacerlos lo más eficientes posible. facilitar las operaciones en paralelo. en vez de hacerlo por lo que son. y manejar las comunicaciones en red. siendo programas de aplicación todos los demás. El sistema operativo se encarga de intercambiar la atención del procesador entre los programas en ejecución y las funciones de apoyo propias. Sin embargo. 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. contabilizar el uso de los recursos. tenemos sistemas de una sola tarea o trabajo. Un sistema de multiprogramación permite que se ejecuten varios trabajos al mismo tiempo. Un objetivo secundario es la utilización eficiente del sistema de computación. 27 LIC. Así.MANUAL DE PROGRAMACIÓN DE SISTEMAS I interfieran recíprocamente. El objetivo principal de un sistema operativo es la comodidad para el usuario. éstos suelen ser muy costosos. recuperarse de los errores. 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. Algunos requieren menos de un megabyte de espacio e incluso carecen de un editor de pantalla. facilitar la entrada y salida. Una perspectiva sencilla considera como tal todo lo que envía un vendedor cuando se ordena la adquisición del "sistema operativo". 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). No existe una definición universal aceptada de qué forma parte de un sistema operativo y qué no. mientras que otros necesitan cientos de megabytes de espacio e incluyen revisores ortográficos y sistemas de ventanas. organizar los datos para lograr un acceso rápido y seguro. planificar la distribución de los recursos. MARTHA MARTINEZ MORENO .

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. 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. Por otra parte se tienen los sistemas de tiempo compartido (multiusuario) que proporcionan acceso interactivo o conversacional a varios usuarios. limitando la intervención del operador solo al montaje de cintas y discos. Estos primeros lenguajes también vinieron acompañados de un nuevo término: Compilador. 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. 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.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Un sistema de multiprocesamiento difiere del sistema de multiprogramación por el hecho de trabajar con varios procesadores a la vez. En este caso. cuando con el advenimiento de computadoras comerciales surge también la necesidad de programarlas. se tienen: sistemas de procesamiento por lotes (batch processing). Otra forma de clasificar a los sistemas operativos toma en cuente el tipo de acceso que proporcionan al usuario. cuando 28 LIC. Esto lo podemos apreciar desde finales de la década de los 50's. 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. cada una de ellas probada individualmente. MARTHA MARTINEZ MORENO .

y lo traduce a un programa equivalente en otro lenguaje. 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). 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. el compilador informa a su usuario de la presencia de errores en el programa fuente. pues. Como parte importante de este proceso de traducción. debido a que han sido completamente traducidos a lenguaje de máquina y no necesitan compartir memoria con el intérprete.MANUAL DE PROGRAMACIÓN DE SISTEMAS I se necesitaba un programa. A grandes rasgos un compilador es un programa que lee un programa escrito es un lenguaje. el lenguaje fuente. se elegían las rutinas necesarias de esa biblioteca y se integraban para conformar el proceso que ejecutaría la computadora. un compilador es un traductor que facilita la comunicación entre el programador y la máquina. 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. MARTHA MARTINEZ MORENO . En nuestros días. Los compiladores pueden ser de: 29 LIC. el término aún se conserva aunque con un sentido ligeramente diferente al planteado por Hopper. Los programas compilados se ejecutan más rápido que los interpretados. Los compiladores son. Para cada lenguaje de programación se requiere un compilador separado. de ahí que los nuevos lenguajes tuviesen sus propios "compiladores" para la integración del proceso que programar representaba. el lenguaje objeto. Quién realizaba este trabajo de acopio de rutinas y de integración se le denominaba compilador. Hoy en día. El compilador traduce todo el programa antes de ejecutarlo.

lo analiza y descubre errores potenciales sin ejecutar el programa. y una pasada final para producir y optimizar el código producido durante los pasos anteriores. Sirve para hacer ampliaciones al lenguaje. Autocompilador: compilador que está escrito en el mismo lenguaje que va a 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++. no se puede ejecutar la primera vez. Ensamblador: el lenguaje fuente es lenguaje ensamblador y posee una estructura sencilla. 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. 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. Lo que sí se han desarrollado son generadores de analizadores • • • • • • • • 30 LIC. Evidentemente. mejorar el código generado. Optimación: lee un código fuente.MANUAL DE PROGRAMACIÓN DE SISTEMAS I • una sola pasada: examina el código fuente una vez. generando el código o programa objeto. El otro tipo de compiladores requiere que todos los enunciados o instrucciones se compilen conjuntamente. Compilador con montador: compilador que compila distintos módulos de forma independiente y después es capaz de enlazarlos. pasadas múltiples: requieren pasos intermedios para producir un código en otro lenguaje. MARTHA MARTINEZ MORENO . Compilador cruzado: se genera código en lenguaje objeto para una máquina diferente de la que se está utilizando para compilar. El desarrollo de los metacompiladores se encuentra con la dificultad de unir la generación de código con la parte de análisis.

• 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. desarrollados para UNIX. MARTHA MARTINEZ MORENO . Los inconvenientes que tienen son que los analizadores que generan no son muy eficientes. UNIDAD III ANÁLISIS LÉXICO EL ANALIZADOR LÉXICO 31 LIC.MANUAL DE PROGRAMACIÓN DE SISTEMAS I léxicos y sintácticos.

En algunos compiladores. espacios en blanco. y la segunda. el analizador léxico se encarga de hacer una copia del programa fuente en el que están marcados los mensajes de error. Elimina del programa fuente comentarios.MANUAL DE PROGRAMACIÓN DE SISTEMAS I El analizador léxico lee los caracteres del programa fuente. palabra reservada etc. 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. "análisis léxico". El analizador Léxico dentro del modelo de un compilador. 32 LIC. mientras que el analizador léxico es el que realiza las operaciones más complejas. 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 examinador se encarga de realizar tareas sencillas. la primera llamada "examen". MARTHA MARTINEZ MORENO . Esta secuencia de caracteres recibe el nombre componente léxico o lexema. Otra función es relacionar los mensajes de error del compilador con el programa fuente. y verifica que correspondan a una secuencia lógica (identificador. el analizador léxico lee los caracteres de entrada hasta que pueda identificar el siguiente componente léxico. caracteres TAB y de línea nueva. En algunas ocasiones. los analizadores léxicos se dividen en dos fases.

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. En ese trabajo se engloba facilitar la definición de la gramática ya que.MANUAL DE PROGRAMACIÓN DE SISTEMAS I El objetivo del análisis léxico es gestionar el "bajo nivel" de la entrada (en este caso. 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.. es decir. La relación entre el analizador léxico y el sintáctico son los tokens y la tabla de símbolos. sólo que previamente se deben considerar las expresiones regulares. buscar palabras clave. 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. el hecho de tener que incluir todas las reglas hasta llegar a los terminales más bajos (caracteres) aumenta indeseablemente la gramática. texto) y suministrarle al analizador sintáctico la misma ya "filtrada". ya que cada unidad léxica puede venir determinada. 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.) se suelen dejar al analizador léxico. Este tipo de trabajos más sencillos (como reconocer números. MARTHA MARTINEZ MORENO .  Hay que considerar si se debe o puede mejorar la eficiencia del compilador. obviar espacios y demás separadores. por una expresión regular. Realmente. conteniendo solamente terminales de la gramática a definir. siempre debe ser regular. más fácil será realizar un compilador. sin embargo estas son algunas consideraciones importantes: Mientras menos complicadas sean las gramáticas.. lo cual nunca debiera ocurrir). La gramática que queda para el análisis léxico.  33 LIC.

MARTHA MARTINEZ MORENO .MANUAL DE PROGRAMACIÓN DE SISTEMAS I  También mejorar la transportabilidad del compilador. 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. y producirá. por lo tanto se manejan dos punteros. Por ejemplo. 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. Bajo esta fase se generan los tokens.  Unidades léxicas (terminales) que pueden contener varios caracteres.  Caracteres que corresponden directamente con la unidad léxica. una entrada del tipo: Si a>b a=c+d: FinEntonces: pasaría a ser Si a>b Entonces a=c+d: FinEntonces: en nuestra lista de entrada. ya que las letras del alfabeto y los errores propios de los dispositivos pueden limitar al analizador léxico.  Cualquier otro carácter es erróneo. uno al último carácter leído y otro al último carácter aceptado Entonces 34 LIC. 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. 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. por ello. Una vez que tenemos la lista de entrada limpia se procede a la creación de los tokens. para ello. La implementación del analizador léxico responde al método general de programación visto en teoría. El trabajo básico es reconocer los terminales (que podemos llamar ya unidades léxicas). un error léxico.  Caracteres que pueden figurar en el texto pero que no se consideran (se desechan).

para separar lo lineal de lo jerárquico. las expresiones regulares son más sencillas y fáciles de entender que una gramática. Los símbolos separadores (espacios. o símbolos terminales de la gramática. 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). Los tokens. Así las ordenes Mira (id) y Mira(id) son tratadas como el mismo token y toman el ``('' como parte de él. 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. MARTHA MARTINEZ MORENO . si algún día fuera modificado el conjunto de símbolos terminales de la gramática.  Eficiencia. han sido representados mediante dos tablas.  35 LIC. una para los que van acompañados de un paréntesis y otra para los que no.MANUAL DE PROGRAMACIÓN DE SISTEMAS I 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.) han sido recogidos del mismo modo en otra tabla dado el trato distinto de los anteriores que recibirán. ``:''. 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. ANÁLISIS LÉXICO: DESARROLLO Objetivo 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. etc. Con esta forma de recoger los tokens que reconoce este analizador hemos conseguido no tener que realizar grandes modificaciones sobre esta fase del compilador. dentro de las órdenes que lleva un parámetro. ¿ Por qué? Formalmente. si entre el final de la orden y el paréntesis que rodea al parámetro hay o no espacios. El motivo de esta separación ha sido el trato distinto que se les ha dado a ambos para no tener que distinguir.

se expanden las macros. ya que la mayoría de diferencias del mismo lenguaje entre distintos entornos suele ser en caracteres especiales. también realiza ciertas funciones secundarias como son eliminar comentarios y caracteres no significativos (espacios. etc. y así se encapsulan en el scanner. 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). capitalización. 36 LIC. A este nivel. 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. relacionado con esto último. para que así pueda responder a la orden "obtener la siguiente unidad léxica" recibida del parser.) o preprocesar las macros.. o intentos de lectura por detrás del fin de fichero. Número finito (quizás 1) de lexemas. los errores se restringen a localizar caracteres no reconocidos. códigos de caracteres. Portabilidad. combinaciones de caracteres imposibles.MANUAL DE PROGRAMACIÓN DE SISTEMAS I   Diseño modular del compilador. en algunos compiladores el scanner va produciendo una copia del fuente en la que se marcan los mensajes de error. etc. La última función del scanner es detectar los errores léxicos que puedan producirse. En este sentido. símbolos especiales. retornos de carro. operadores. lo más habitual es convertir al scanner en una subrutina del parser. tabuladores.. Tipos de unidades léxicas: concepto de patrón Hay dos clases de unidades léxicas:  Las definidas por el lenguaje: palabras clave. MARTHA MARTINEZ MORENO . Como el scanner lee el programa fuente.

1 34. El patrón permite identificar el token y asignar el atributo (suponiendo delimitado el lexema). Normalmente. 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. 23. cumplirá algún patrón. Por ejemplo. de las segundas. seguido opcionalmente de 'E' o 'e' y dígitos 37 LIC. Se dice que el patrón concuerda con el lexema que cumple ese patrón. El patrón de las primeras es la simple enumeración de los lexemas. De este modo. tomando un subconjunto de Pascal: Token pc-then pc-while pc-write pc-incdec op-rel par-ab par-cer ident lit-ent lit-real Lexemas ejemplo then while write to downto < > ( ) Hola. pepe 34. una serie de normas que describen todas las cadenas que corresponden al mismo token. y ese es el que marca el token. el número "posible" de lexemas es infinito.4e-5 Descripción del patrón (t/T)(h/H)(e/E)(n/N) while write to downto < > ( ) Letra seguida de letras y dígitos Dígito no cero seguido de dígitos Dígito no cero seguido de dígitos seguido opcionalmente de punto decimal y dígitos. Es decir.MANUAL DE PROGRAMACIÓN DE SISTEMAS I  Las definidas por el programador (bajo ciertas normas genéricas): identificadores. 23442. MARTHA MARTINEZ MORENO . A77aH. una vez delimitado el lexema. literales.0. comentarios.

Por supuesto. "A". Dado cada uno de estos lexemas. etc.  38 LIC. "4". no forman parte de ningún lexema ni constituyen un token) pero sí son separadores. los espacios (y también tabuladores y retornos) no son significativos (es decir. cuáles son los separadores. 'pe pe ' Caracteres entre '. simplemente observando las series de caracteres separadas por espacios. literales y signos de puntuación. podríamos pensar en utilizar un carácter fijo para delimitar lexemas. en PL/I). "50". MARTHA MARTINEZ MORENO . "write". la influencia de las distintas normas léxicas: si las palabras clave pueden usarse como identificadores. ")". "then". el scanner debe distinguir entre palabras clave e identificadores.MANUAL DE PROGRAMACIÓN DE SISTEMAS I lit-string 'Hola'. Las palabras clave suelen ser reservadas. por ejemplo el espacio. Normas léxicas El problema ahora es ¿cómo delimitamos el lexema? En un principio. excepto ' En la mayoría de los lenguajes se consideran tokens las construcciones: palabras clave. else else = then. Vamos a enunciar brevemente las más influyentes en este sentido:  En general. De este modo. operadores. lo cual suele ser complicado. identificadores. Por ejemplo: if then then then = else . dependiendo del lenguaje se subdividirán en varios componentes léxicos más. "(". 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. En este funcionamiento es fundamental. será muy sencillo ver si cumple algún patrón y devolver el token y atributo correspondiente al patrón que se cumple. "<". por ello. la sentencia if A < 50 then write ( 4 ) puede verse fácilmente separada en los lexemas "if". De hecho. Cuando no lo son (por ejemplo.

ni por and . conocidos también como máquinas de estado finito o (con menos frecuencia en la actualidad) máquinas secuenciales.  En caso de conflicto. Son las posibles cadenas que acepta un lenguaje. 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ímbolos qo estado inicial F conjunto de estados finales 39 LIC. (imaginemos lo que ocurriría con lenguajes con muchas palabras reservadas). no puede diferenciar entre identificadores y palabras clave. se elige el lexema más largo. Hay dos herramientas formales que nos permiten hacer este trabajo de un modo muy sencillo:  Los autómatas finitos. El problema del análisis léxico. Por ejemplo. se reduce entonces a reconocer el cumplimiento de patrones por determinados lexemas.. 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. MARTHA MARTINEZ MORENO . ni por if.. 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 or. o bien diferenciarlos le supone trabajo "extra".MANUAL DE PROGRAMACIÓN DE SISTEMAS I El scanner. Es la representación gráfica de las posibles cadenas que acepta un lenguaje dentro de un conjunto de símbolos. ifa es el identificador ifa y no la palabra clave if seguida del identificador a.

Una expresión regular es una fórmula para denotar "ciertos" lenguajes. No todos los lenguajes pueden ser expresados utilizando una expresión regular. Una expresión regular única denota un conjunto de cadenas. Adviértase que decimos lenguaje. al conjunto R*. r+s (r) rs r* desde es es es es una una una una expresión expresión expresión expresión regular regular regular regular que que que que denota denota denota denota a los conjuntos R ∪ S.MANUAL DE PROGRAMACIÓN DE SISTEMAS I  Las expresiones regulares. Si r y s son expresiones regulares que denotan a los lenguajes R y S.. a sí mismo. La expresión regular sobre ∑ y los conjuntos que denotan se definen de manera recursiva como sigue: 1. respectivamente. Para cada a ∈ ∑. 2. Conjunto de símbolos para aceptar una palabra reservada. entonces tenemos lo siguiente. MARTHA MARTINEZ MORENO . Son los posibles símbolos que puede aceptar un lenguaje los cuales se pueden representar con un autómata. ri es una expresión regular que denota al conjunto Ri. Las expresiones regulares se pueden usar para especificar unidades léxicas presentes en un lenguaje de programación. 40 LIC. a los conjuntos RS. ε es una expresión regular y denota al conjunto vacío {ε }. Sea un alfabeto ∑. al conjunto R. a es una expresión regular y denota al conjunto {a}. 3. no una simple cadena. no cadena de caracteres. es decir un lenguaje. equivale a una o más repeticiones del lenguaje R. r+ es una expresión regular que denota al conjunto R+. equivale cero a más repeticiones del lenguaje R.

este se introduce e la tabla de símbolos. MARTHA MARTINEZ MORENO . 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. 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. velocidad: integer . R. dada la siguiente instrucción: Var inicial. sus atributos no pueden determinarse durante el análisis léxico.MANUAL DE PROGRAMACIÓN DE SISTEMAS I 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. Punt. las fases restantes introducen información sobre los tokens en la tabla y después hace uso de ella. R. Por ejemplo. Separador inicial Id Var P. Posición Token Valor en memoria . velocidad Id . Var Utilizado por El tipo entero no se conoce cuando el analizador léxico pasa por toda la instrucción. sin embargo. Delimitador integer P. Int : S. MÉTODOS GENERALES PARA LA IMPLANTACIÓN DE UN ANALIZADOR LÉXICO 41 LIC.

MARTHA MARTINEZ MORENO . 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"). b) Describir el analizador léxico en un lenguaje convencional de programación utilizando subrutinas de entrada / salida. en la forma de unidades léxicas que determina el analizador léxico. como se muestra en la siguiente figura: 42 LIC. c) Escribir el analizador léxico en lenguaje ensamblador.MANUAL DE PROGRAMACIÓN DE SISTEMAS I 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.

MANUAL DE PROGRAMACIÓN DE SISTEMAS I El Analizador Sintáctico dentro del modelo de un compilador. realizar la verificación de tipo y otras clases de análisis semántico. son demasiado ineficientes para usarlos en la producción de compiladores. Ascendente: construye el árbol desde las hojas hacia la raíz. el programa es (sintácticamente) correcto. como el algoritmo de Cocke-Younger-Kasami y el de Earley. En ambos casos. se examina la entrada al analizador sintáctico de izquierda a derecha. En la práctica. MARTHA MARTINEZ MORENO . Los métodos empleados generalmente en la producción de compiladores se clasifican como descendentes o ascendentes. Si esto se consigue. Los métodos universales de análisis sintáctico. un símbolo a la vez. como recoger información sobre distintos componentes léxicos en la tabla de símbolos. pueden analizar cualquier gramática. hay varias tareas que se pueden realizar durante el análisis sintáctico. Estos métodos sin embargo. Los métodos descendentes y ascendentes más eficientes trabajan sólo con subclases de gramáticas. y es incorrecto en caso contrario. Para la implementación del analizador sintáctico debe de ser necesaria la 43 LIC. Existen tres tipos generales de analizadores sintácticos para 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. Los dos tipos principales de análisis sintáctico son: Descendente: construye el árbol desde la raíz hacia las hojas. También deberá recuperarse de los errores que ocurren frecuentemente para poder continuar procesando el resto de su entrada. Se supone que el analizador sintáctico informará de cualquier error de sintaxis de manera inteligible.

que pueden detectar la presencia de errores dentro de los programas de una forma muy eficiente. como una expresión aritmética con paréntesis no equilibrados. Otra razón es la precisión de los métodos modernos de análisis sintáctico. Sintácticos. el manejador de 44 LIC. Semánticos. palabra clave u operador.MANUAL DE PROGRAMACIÓN DE SISTEMAS I redefinición de la gramática dada en la práctica de modo que sea ``entendible'' por la computadora. 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. en algunos casos un error pudo haber ocurrido mucho antes de la posición en que se detectó su presencia. sin embargo. como escribir mal un identificador. A menudo. MARTHA MARTINEZ MORENO . Afortunadamente lo errores más comunes son simples y a menudo basta con un mecanismo sencillo de manejo de errores. 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. gran parte de la detección y recuperación de errores en un compilador se centra en la fase de análisis sintáctico. como una llamada infinitamente recursiva. Se sabe que los programas pueden contener errores de muy diversos tipos. como un operador aplicado a un operando incompatible. En los casos difíciles. y puede ser muy difícil deducir su naturaleza precisa de error. Por ejemplo los errores pueden ser: • • • • Léxicos. Lógicos. Dentro de la gramática generada se deberá eliminar la recursividad a izquierdas. 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. 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.

MARTHA MARTINEZ MORENO . ¿ Cómo se debe de recuperar el analizador 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. 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 razonable de que hará el análisis de la entrada correcta o de que será manejada correctamente por el compilador. teniendo en cuenta la clase de errores que se pueden presentar y que sean de procesamiento razonable.MANUAL DE PROGRAMACIÓN DE SISTEMAS I errores quizá tenga que adivinar qué tenía en mente el programador cuando escribió el programa. Algunos compiladores intentan reparar el error proceso en el cual el compilador intenta adivinar lo que pretendía escribir el programador. Después de descubrir un error sintáctico. porque el posterior procesamiento de la entrada podría revelar más errores. En la mayoría de los casos. porque es muy probable que el error real se haya producido en alguno de los componentes léxicos anteriores. puede haber demasiados errores como para que el compilador continúe un procesamiento razonable. 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. al menos debe informar del lugar en el programa fuente donde se detecta el error. Normalmente. ¿Cómo debe informar un manejador de errores de la presencia de un error ?. tienen la propiedad del prefijo viable. Es decir. también se incluye un mensaje de diagnóstico informativo y comprensible. 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. Parece que una estrategia de recuperación de errores tiene que ser un compromiso cuidadosamente considerado. Si hay una posibilidad razonable de saber cuál es realmente el error. no es adecuado que el analizador sintáctico abandone después de detectar el primer error. 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. Varios métodos de análisis sintáctico. detectan un error lo antes posible. por ejemplo "Falta punto y coma en esta posición". En algunos casos. Una vez detectado el error. 45 LIC.

Estos componente léxicos de sincronización son generalmente delimitadores. el analizador sintáctico puede realizar una corrección local de la entrada restante. Por supuesto se debe de tener cuidado de elegir sustituciones que no conduzcan a lazos infinitos. Una corrección local típica sería sustituir una coma por un punto y coma. hasta que encuentra uno perteneciente a un conjunto designado de componente léxicos de sincronización. Este tipo de sustitución puede corregir cualquier cadena de entrada y ha sido empleado en varios compiladores que corrigen los errores. suprimir un punto y coma sobrante e insertar un punto y coma que falta. este método puede resultar bastante adecuado. es decir. a diferencia de otros métodos. La elección de la corrección local corresponde al diseñador del compilador. El método se usó por primera vez en el análisis sintáctico descendente. algunos métodos tienen una amplia aplicabilidad. Mencionaremos algunas estrategias. Es evidente que quien diseña el compilador debe seleccionar los componentes léxicos de sincronización adecuados para el lenguaje fuente. 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. puede sustituir un prefijo de la entrada restante por alguna cadena que permita continuar al analizador sintáctico. cuyo papel en el programa fuente está claro. En situaciones en donde son raros los errores múltiples en la misma proposición. • 46 LIC. Recuperación a nivel de frase: Al descubrir un error. Su principal desventaja es su dificultad para afrontar situaciones en que el error real se produjo antes del punto de detección. esta garantizado contra lazos infinitos.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Hay muchas estrategias generales distintas que puede emplear un analizador sintáctico para recuperarse de un error sintáctico. MARTHA MARTINEZ MORENO . Aunque la corrección en modo de pánico a menudo omite una cantidad considerable de entrada sin comprobar la existencia de errores adicionales. como el punto y coma o la palabra clave end. tiene la ventaja de la sencillez y. Al descubrir un error. el analizador sintáctico desecha símbolo de entrada. Aunque ninguna de ellas ha demostrado ser la aceptación universal. de uno en uno.

Sin embargo. predicados. MARTHA MARTINEZ MORENO . tal que el número de inserciones. una gramática es un dispositivo formal para expresar un lenguaje potencialmente infinito. así que estas técnicas en la actualidad sólo son de interés teórico. Al mismo tiempo un gramática impone una estructuras a la sentencias en el 47 LIC. en una manera finita. ya sea español inglés o Pascal. se puede aumentar la gramática del lenguaje con producciones que generen la construcciones erróneas. la noción de corrección de costo mínimo proporciona una escala para evaluar las técnicas de recuperación de errores. puesto que es imposible enumerar todas las cadenas de caracteres en un lenguaje.MANUAL DE PROGRAMACIÓN DE SISTEMAS I • Producciones de error: Si se tiene una buena idea de los errores comunes que puede encontrarse. son con frecuencia descritos por una gramática que agrupa las palabras en categorías sintácticas tales como sujetos. etc. y se ha usado para encontrar cadenas de sustitución óptimas para recuperación a nivel de frase. frases preposicionales. Los lenguajes naturales como el español o el ingles. Dada una cadena de entrada incorrecta x y la gramática G. Gramáticas. estos algoritmos encontrarán un árbol de análisis sintáctico para una cadena relacionada y. Expresándolo en forma matemática. Entonces se usa esta gramática aumentada con las producciones de error para construir el analizador sintáctico. • Se debe señalar que un programa correcto más parecido al original puede no ser lo que el programador tenía en mente. 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. Las gramáticas describen lenguajes. la implantación de estos métodos es en general demasiado costosa en términos de tiempo y espacio. Existen algoritmos para elegir una secuencia mínima de cambios para obtener una corrección global de menor costo. Por desgracia. sería deseable que un compilador hiciera el mínimo de cambios posibles al procesar una cadena de entrada incorrecta. supresiones y modificaciones de componentes léxicos necesarios para transformar x en y sea el mínimo posible.

los lenguajes recursivos contienen propiamente a los lenguajes sensibles al contexto. De una manera más general: Una gramática es un sistema para definir un lenguaje.P. β ε (N U Σ )+ y | α | <= | β |. S} donde: N : alfabeto de no-terminales "∀ n ∈ N à n ∉ Σ Σ : alfabeto terminal P : Conjunto finito de producciones consistente de expresiones de la forma α β → con α ∈ (N ∪ Σ )+ y β ∈(N Σ ∪ )* S : símbolo inicial S ∈ N N∩S = φ GRAMÁTICAS Y LENGUAJES SENSIBLES AL CONTEXTO.P) donde: N: Colección de símbolos no terminales. Es decir una gramática.MANUAL DE PROGRAMACIÓN DE SISTEMAS I lenguaje. Los lenguajes sensibles al contexto contienen. Σ : Alfabeto del lenguaje. donde α . propiamente. 48 LIC. P: Conjunto de producciones si todas las producciones son de la forma β→ α . a los lenguajes independientes al contexto a su vez. Una gramática G es una 4-tupla G = {N. G. S: Símbolo especial llamado símbolo inicial.S . como también una herramienta para imponer a las cadenas del lenguaje una estructura útil. S . MARTHA MARTINEZ MORENO . DEFINICION FORMAL MATEMATICA DE LAS GRAMÁTICAS SENSIBLES AL CONTEXTO (CSG’S) Es una 4-TUPLA formada por (N. define un lenguaje L(G) mediante la definición de una manera de para derivar todas las cadenas en el lenguaje. Una forma de definir un lenguaje es mediante un reconocedor que acepte las palabras que pertenecen a él.Σ .

MANUAL DE PROGRAMACIÓN DE SISTEMAS I El conjunto de los lenguajes sensibles al contexto contiene el conjunto de los lenguajes independientes del contexto. Clasificación de las Gramáticas. lenguajes libres de contexto y lenguajes 49 LIC. A veces es conveniente que la cadena pertenezca al lenguaje que ha sido generado por una gramática sensible al contexto. Lenguaje Lenguajes Gramática Autómata Ejemplo a* Detector Gramática Regular deterministico ó no deterministico finito + Gramática-lineal Derecha + Gramática-lineal Izquierda + Gramática libre de contexto +Gramática Sensible al contexto + Gramática norestringida Autómata pushdown no deterministico Autómata LinealVinculada Maquina de Turíng Regulares Lenguaje libre de contexto Lenguaje sensible al contexto Lenguajes Recursivos Enumerables ab abc Cualquier Función Computable Jerarquía de Chomsky. Estos lenguajes sirven como base para la clasificación de lenguajes de programación. para cualquier gramática que es  sensible al contexto.1. MARTHA MARTINEZ MORENO . 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. lenguajes sensibles al contexto. En 1959. Puesto que la cadena vacía tiene longitud cero. Los cuatro tipos son: lenguajes recursivamente enumerables. Noam Chomsky clasificó las gramáticas en cuatro tipos de lenguajes y esta clasificación es conocida como la jerarquía de Chomsky. en la cual cada lenguaje es descrito por el tipo de gramática generado. podemos deducir de la definición que ∉ L(G). tal como se muestra en al Tabla 3.

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 reconocidos por A son de tipo T. 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. 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. MARTHA MARTINEZ MORENO . lo que implica que un símbolo puede reemplazarse en el contexto de otros.2 y 3. La correspondencia entre lenguajes y arquitectura son mostrados en la siguiente tabla: Tipo Lenguajes 0 1 2 3 Tipo Máquina de de Recursivamente Máquina Enumerables Turing Sensibles contexto Libres contexto Lenguajes al Autómata Lineal Acotado de Autómatas de Pila Autómatas 50 LIC. Con esto se impide que las cadenas que se obtengan en el transcurso de la aplicación de las reglas sean de longitud decreciente.MANUAL DE PROGRAMACIÓN DE SISTEMAS I regulares. Dichos lenguajes también se identifican como lenguajes de tipo 0.1. Las gramáticas sensibles al contexto reciben el nombre de gramáticas de tipo 1. y se impide así mismo el caso extremo de que mediante una gramática de tipo1 puedan desaparecer cadenas de símbolo. porque del lado izquierdo puede haber más de un elemento.

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. Regulares Sobre un alfabeto dado. MARTHA MARTINEZ MORENO . tal como se muestra: Gramáticas Ambiguas 51 LIC. que a su vez contiene propiamente a los lenguajes regulares.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Finitos y Expresiones Regulares Los cuatro tipos de gramáticas.

se dice que es ambigua. 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: 52 LIC. Si una sentencia es ambigua. "vuela" como sustantivo. se asigna significado a las construcciones en los lenguaje de programación con base en sus sintaxis. Sin embargo.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Si una oración en español tiene mas de un significado. Puede interpretarse tiempo como sustantivo. Esta interpretación es un comentario acerca del rápido paso del tiempo. podemos crear mas de un árbol sintáctico para la misma sentencia. Para ver lo anterior consideraremos esta gramática. la oración se convertiría en un comentario acerca de la vida amorosa de alguna especie. MARTHA MARTINEZ MORENO . Una sentencia es ambigua si hay mas de una derivación distinta. preferimos que las gramáticas de los lenguajes de programación describan programas sin ambigüedad alguna. Una gramática se dice que es ambigua si hay dos o más árboles de derivación distintos para la misma cadena. "como" como un verbo y flecha como un sujeto directo. En forma similar. como una frase adverbial. La oración: El tiempo vuela como una flecha. Con frecuencia tales oraciones pueden analizarse sintácticamente en mas de una forma. su árbol de análisis sintáctico no es único. si "tiempo" se interpreta como adjetivo. vuela como verbo y una flecha. Por consiguiente.

MANUAL DE PROGRAMACIÓN DE SISTEMAS I Primer árbol de derivación para la gramática dada. MARTHA MARTINEZ MORENO . aunque las cadenas producida son la misma. La ambigüedad puede ser un problema para 53 LIC. Obsérvese que los dos árboles son distintos. 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.

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. como ocurre con los lenguajes naturales y los lenguajes de programación. PRODUCCIONES: Se pueden pensar como un conjunto de reglas de reemplazo. de su estructura. La gramática independiente del contexto: S à ASB |SS | ε Bà b Es ambigua. 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. lo único que hay que hacer es encontrar una cadena de componentes léxicos que tanga mas de un árbol sintáctico. NO TERMINALES: Los no-terminales son los nodos no-hojas en un árbol de análisis sintáctico. 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. MARTHA MARTINEZ MORENO . Por desgracia. Para demostrar que una gramática es ambigua. Si la estructura de un lenguaje tienen más de una composición y si la construcción parcial determina su significado. en parte. ya que hay dos derivaciones por la izquierda de a2b2.MANUAL DE PROGRAMACIÓN DE SISTEMAS I ciertos lenguajes en los que su significado depende. Como una cadena que cuenta con mas de un árbol sintáctico suele tener mas de un significado. en general no es posible determinar si una gramática independiente del contexto es ambigua. 54 LIC. 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.

Para construir un árbol de análisis sintáctico descendente para esta cadena. el segundo símbolo de w. reemplace el no terminal del lado izquierdo en la forma sentencial actual. MARTHA MARTINEZ MORENO . 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. SENTENCIA: una sentencia es una forma que consiste solamente de terminales tales como a + a * a MÉTODOS DESCENDENTES O TOP-DOWN. Se empareja la hoja más a la izquierda.terminal especial diseñado como el único a partir del cual todas las cadenas son derivadas. también denominado símbolo meta es un no. primero se crea un árbol formado por un solo nodo etiquetado con S. FORMA SENTENCIAL: Una forma sentencial es cualquier cadena de caracteres derivada desde el símbolo de inicio. 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. En cada paso. los pasos son: 1) 2) Comenzar con el símbolo de inicio como la “raíz” del árbol de análisis sintáctico. etiquetada con c. con el primer símbolo de w. y se considera la siguiente hoja 55 LIC. Un apuntador a la entrada apunta a c. El análisis sintáctico descendente crea una derivación más a la izquierda.(a). 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. Por ejemplo considere la gramática: S → cAd A → ab | a y la cadena de entrada w = cad.MANUAL DE PROGRAMACIÓN DE SISTEMAS I SÍMBOLO DE INICIO: El símbolo de partida o de inicio. y a continuación se aproxima el apuntador de entrada a a.

Como b no concuerda con d. Al regresar a A. aquella que tenia al ir a A por primera vez. MARTHA MARTINEZ MORENO . (c). Pasos en el Análisis Sintáctico Descendente. Se empareja la hoja a con el segundo símbolo de w. el tercer símbolo de la entrada. se lleva el apuntador de entrada a d. Análisis Sintáctico por Descenso Recursivo. se indica fallo y se regresa a A para saber si existe otra alternativa de A que no se haya intentado. (b). Un analizador sintáctico de descenso recursivo se compone de un procedimiento para cada símbolo no terminal de la gramática. se para y se anuncia el éxito de la realización completa del análisis sintáctico.MANUAL DE PROGRAMACIÓN DE SISTEMAS I etiquetada con A. cuando se 56 LIC. etiquetada con b. pero se puede dar lugar a un emparejamiento. se debe restablecer el apuntador de entrada a la posición 2. con el tercer símbolo. Entonces se puede expandir A utilizando la primera alternativa de A para obtener el árbol de la Fig. y la hoja d. y se compara d con la hoja siguiente. se intenta a continuación la segunda alternativa de A para obtener el árbol de la Fig. Como ya se tiene una concordancia para el segundo símbolo de la entrada. Como ya se ha producido un árbol de análisis sintáctico par w.

En otro caso el procedimiento devuelve una indicación de fallo. Si un procedimiento encuentra el no terminal que pretende. empezando por el componente léxico actual. El procedimiento es ligeramente más complicado cuando hay varias opciones definidas por la gramática para un no terminal. que se pueda interpretar como el no terminal con el cuál está asociado al procedimiento.MANUAL DE PROGRAMACIÓN DE SISTEMAS I llama un procedimiento este intenta encontrar una subcadena de la entrada. y también avanza el apuntador al componente léxico situado después de la subcadena que acaba de reconocer. devuelve una indicación de fallo o invoca a una rutina de diagnóstico y recuperación de errores. examina el siguiente componente léxico buscando un ).id que definen la sintaxis de la proposición READ del lenguaje PASCAL. A modo de ejemplo considérese la siguiente gramática: <lect> ::=READ(<lista_id)> <lista_id> ::=id | <lista_id>. 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 ). Para la técnica de descenso recursivo. devuelve una indicación con éxito a quién lo llamó. entonces el procedimiento para <lect> llama el procedimiento para <lista_id>. Si el procedimiento es incapaz de encontrar una subcadena que pueda interpretarse como el no terminal deseado. Si ese procedimiento tiene éxito. Hay otros métodos descendentes que eliminan este requisito. 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 (. se debe poder decidir qué opción utilizar examinando el siguiente componente léxico de entrada. aunque no son tan 57 LIC. En este caso el procedimiento debe decidir que opción intentar. el procedimiento <lect>. Si se encuentran. para buscar otros no terminales. MARTHA MARTINEZ MORENO . Durante este proceso puede llamar a otros procedimientos o llamarse a sí mismo recursivamente.

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>. pues tanto id. y después sigue explorando la entrada mientras los dos siguientes componentes léxicos sean una como(. se descubriría un problema.) e id. 58 LIC. sería incapaz de decidir entre sus dos opciones. MARTHA MARTINEZ MORENO . si el procedimiento hubiera decidido intentar la segunda opción <(lista_id. el procedimiento para <lista_id> simplemente busca primero un id. que daría lugar a una cadena sin fin.id} Esta notación. Con la definición revisada. id <mas_id>|ε Pudiéndose representarse también así: <lect> ::= READ(<lista_id>) <lista_id> ::= id{.MANUAL DE PROGRAMACIÓN DE SISTEMAS I eficientes como el descendente recursivo. que es una extensión común a BNF.id". Sin embargo hay una dificultad más importante. Si se intentara escribir los procedimientos para la gramática anterior. 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> ::= . Los analizadores sintácticos descendentes no pueden usarse directamente con una gramática que contenga esta clase de recursión por la izquierda inmediata. especifica que los términos entre { y } se pueden omitir o repetir una o mas veces. El procedimiento para <lista_id>. se llamaría de inmediato a si mismo recursivamente para encontrar una <lista_id>. como <lista_id> pueden comenzar con id. Esto podría producir otra llamada recursiva inmediata.id)>. De esta forma la gramática define a <lista_id> compuesta de un id seguida de cero o mas ocurrencias de ". La razón de esto es que una de las opciones para <lista_id> empieza con <lista_id>.

59 LIC. 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). LISTA_ID ha vuelto a LECT indicando que ha tenido éxito. con esto se completa el análisis de la proposición fuente. indicando que se encontró satisfactoriamente un <lect>. por lo que LECT ha examinado el componente léxico de entrada ). LECT ha llamado a LISTA_ID (indicado con línea continua). que ha examinado el componente id.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Análisis Sintáctico descendente recursivo de una proposición READ. MARTHA MARTINEZ MORENO . anterior ilustra el análisis sintáctico descendente recursivo de la proposición READ. En el apartado (2). En el apartado (1). con la gramática anterior. 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. En el apartado (3). El procedimiento READ regresara ahora a quién lo llamó. La Fig.

a cada símbolo no terminal de una gramática se le asocia con una producción. El modelo sintáctico predictivo puede verse en la figura de abajo. 60 LIC. Es una forma eficiente de implementar el análisis sintáctico descendente implícitamente manteniendo una pila. Id := Expresión Termino Expresión' + Termino Expresión' | ε Factor Termino' * Factor Termino' | ε ( Expresión ) Id Literal Análisis sintáctico Predictivo. Un analizador sintáctico es guiado por una tabla en la que se encuentran las producciones. 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. en vez de hacerlo implícitamente mediante llamadas recursivas.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Ejemplo Descendente Recursivo Gramática Programa Sentencias Sentencias' Sentencia Asignación Expresión Expresión' Termino Termino' Factor → → → → → → → → → → | | Sentencias Sentencia Sentencias' Sentencias | ε Asignación . MARTHA MARTINEZ MORENO .

M [símbolo_no_terminal . el símbolo de la entrada actual. El elemento inicial de la gramática debe de ser la cima de la PILA. además de la PILA. El analizador sintáctico predictivo tiene una ENTRADA. El programa determina a X. Estos dos símbolos determinan la acción del analizador sintáctico: hay tres posibilidades: 1. Esta entrada podría ser una 61 LIC. el analizador sintáctico saca a X dela pila y avanza el apuntador de entrada al siguiente símbolo de entrada. Mantiene una PILA en vez de hacer llamadas recursivas.a] de la tabla de análisis sintáctico.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Modelo de un analizador sintáctico predictivo. MARTHA MARTINEZ MORENO . La Pila contiene una secuencia de símbolos de alguna gramática que debe de finalizar con un $ (delimitador). 2. símbolo_terminal_$] El analizador sintáctico esta controlado por un programa que trabaja como sigue.Si X = a = $. y a. una TABLA de análisis sintáctico y una SALIDA. 3.Si X es un símbolo no terminal el programa consulta la entrada M[X. La TABLA es un arreglo bidimensional (matriz). Características: Es una forma eficiente de un analizador sintáctico recursivo.Si X = a≠ $. el analizador sintáctico se detiene y anuncia el éxito de la realización del análisis sintáctico. el símbolo en la cima de la pila.

a] = error el analizador sintáctico llama a una rutina de recuperación de error. Inicialmente el analizador sintáctico esta en la configuración: Pila Entrada $S w$ 62 LIC. Esta entrada podría ser una producción cualquiera dela gramática o un error. Gramática E → TE' E' → +TE' | ξ T → FT' T' → ∗FT | ξ F → (E) | id Algoritmo 1. el analizador sintáctico reemplaza a X en la cima dela pila por WVU (con U en la cima). el analizador sintáctico se detiene y anuncia el éxito de la realización del análisis sintáctico. Si M[X. Si M[X. la cual es dada por el contenido de la pila y la entrada restante. Se puede describir el comportamiento del analizador sintáctico en términos de su configuración. Si X = a = $. Si M[X.a] = error el analizador sintáctico llama a una rutina de recuperación de error.a] = {X→UVW}. el analizador sintáctico reemplaza a X en la cima de la pila por WVU (con U en la cima). 2. Se puede describir el comportamiento del analizador sintáctico en términos de su configuración.MANUAL DE PROGRAMACIÓN DE SISTEMAS I producción cualquiera de la gramática o un error. la cual es dada por el contenido de la pila y la entrada restante. Si M[X. 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. MARTHA MARTINEZ MORENO .el analizador sintáctico saca a X de la pila y avánzale apuntador de entrada al siguiente símbolo de entrada.a] de la tabla de análisis sintáctico.a] = {X→UVW}. Si X es un símbolo no terminal el programa consulta la entrada M[X. Si X = a ≠ $. 3.

MARTHA MARTINEZ MORENO . y reemplazamos X = T por su producción la cual es FT'. ocupamos la regla 3 del algoritmo. y tendremos en la pila "$ E' T". Y en la entrada tenemos a la cadena completa. Matriz de precedencia resultante id E E´ T T´ F 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 "$". Introduciremos la producción en el orden inverso. y la salida será la producción del no terminal "E". así que tenemos en la pila a "$E". y reemplazamos X = E por su producción la cual es TE'. Introduciremos la 63 LIC.MANUAL DE PROGRAMACIÓN DE SISTEMAS I donde S es el símbolo de inicio dela gramática y w es la cadena que ha de ser analizada. Paso 3 En la cima de la pila tenemos a "T" verificamos en la gramática y tenemos que es un no terminal. Paso Pila Entrada Salida 1 $E id + id * id $ F → id T → FT´ T´ → ξ T´→ *FT´ F →( E ) E → TE´ E´→ +TE ´ T → FT´ T´→ ξ T´ξ→ + ∗ ( E → TE´ E´→ ξ E´→ ξ ) $ 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 = T por su producción la cual es FT'. Introduciremos la producción en el orden inverso. y tendremos en la pila "$ E' " ó "$ E' ξ ". y reemplazamos X = T' por su producción la cual es ξ . Paso 9 En la cima de la pila tenemos a " T " verificamos en la gramática y tenemos que es un no terminal. 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. ahora tendremos en la pila "$ E' T' id". 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 4 Como la cima de la pila esta X= F y este produce el símbolo de la cadena leída. Introduciremos la producción en el orden inverso. MARTHA MARTINEZ MORENO . y tendremos en la pila "$ E' T +" y la salida será la producción del no terminal " E' ". ocupamos la regla 3 del algoritmo. y reemplazamos X = E' por su producción la cual es +TE'.MANUAL DE PROGRAMACIÓN DE SISTEMAS I producción en el orden inverso. Introduciremos la producción en el orden inverso. ocupamos la regla 3 del algoritmo. ocupamos la regla 3 del algoritmo. y tendremos en la pila "$ E' T' F". Paso 6 En la cima de la pila tenemos a "T'" verificamos en la gramática y tenemos que es un no terminal. y tendremos en la pila "$ E' T' F" y la salida será la producción del no terminal " T ". se sustituye por su producción que es el terminal "id" y 64 LIC. se sustituye por su producción que es el terminal "id" y avanza el apuntador de entrada al siguiente símbolo de la cadena. Paso 7 En la cima de la pila tenemos a " E' " verificamos en la gramática y tenemos que es un no terminal. y la salida será la producción de "F". 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. 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 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. ocupamos la regla 3 del algoritmo. ocupamos la regla 3 del algoritmo. y tendremos en la pila "$ E' T' F *" y la salida será la producción del no terminal " T' ". ahora tendremos en la pila "$ E' T' id". Introduciremos la producción en el orden inverso. y tendremos en la pila "$ E'" ó "$ E' ξ " y la salida será la producción del no terminal " T' ". y la salida será la producción de "F". MARTHA MARTINEZ MORENO . se sustituye por su producción que es el terminal "id" y avanza el apuntador de entrada al siguiente símbolo de la cadena. Introduciremos la producción en el orden inverso. 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 16 En la cima de la pila tenemos a " T' " verificamos en la gramática y tenemos que es un no terminal. Paso 12 En la cima de la pila tenemos a " T' " verificamos en la gramática y tenemos que es un no terminal.MANUAL DE PROGRAMACIÓN DE SISTEMAS I avanza el apuntador de entrada al siguiente símbolo de la cadena. y reemplazamos X = T' por su producción la cual es *FT'. ocupamos la regla 3 del algoritmo. y reemplazamos X = E' por su producción la cual es ξ . y la salida será la producción de "F". Paso 17 En la cima de la pila tenemos a " E' " verificamos en la gramática y tenemos que es un no terminal. Introduciremos la 65 LIC. Paso 14 Como la cima de la pila esta X= F y este produce el símbolo de la cadena leída. y reemplazamos X = T' por su producción la cual es ξ . ahora tendremos en la pila "$ E' T' id".

MÉTODOS ASCENDENTES O BOTTOM UP. Paso 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Pila $E $ E' T $ E' T' F $ E' T' Id $ E' T' $ E' $ E' T + $ E' T $ E' T' F $ E' T' Id $ E' T' $ E' T' F * $ E' T' F $ E' T' Id $ E' T' $ E' $ Entrada id + id * id $ id + id * id $ id + id * id $ id + id * id $ + id * id $ + id * id $ + id * id $ id * id $ id * id $ id * id $ * id $ * id $ id $ id $ $ $ $ T´→ ξ E´→ ξ F → id T´→ *FT´ T → FT´ F → id T´ → ξ E´→ +TE´ E → TE´ T → FT´ F → id Salida 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. y tendremos en la pila "$ E'" ó "$ E' ξ " y la salida será la producción del no terminal " T' ".MANUAL DE PROGRAMACIÓN DE SISTEMAS I producción en el orden inverso. MARTHA MARTINEZ MORENO . Se puede considerar este proceso como de "reducir" una cadena w al símbolo inicial de la gramática. entonces el analizador sintáctico se detiene y anuncia el éxito de la realización del análisis de la cadena. En cada paso de reducción se sustituye una subcadena 66 LIC. 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).

El problema 67 LIC. b y d concuerdan con el lado derecho de alguna producción. Considérese la siguiente gramática: S → aABe A → Abc | b B →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. se traza una derivación por la derecha en sentido inverso. La subcadenas b y d sirven. así se obtiene la cadena aAbcde. se obtiene aABe. las subcadenas Abc. 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. Aunque b es la subcadena situada más a la izquierda que concuerda con el lado derecho de una producción. ahora se puede sustituir toda la cadena por S. mediante una secuencia de cuatro reducciones se puede reducir abbcde a S. Sustituyendo después d por B que es el lado izquierdo de la producción B → d.MANUAL DE PROGRAMACIÓN DE SISTEMAS I determinada que concuerde con el lado derecho de una producción por el símbolo del lado izquierdo de dicha producción y si en cada paso se elige correctamente la subcadena. A continuación. se elige sustituir la subcadena Abc por A. el lado izquierdo de la producción A → b. De hecho. usando una regla U → u . MARTHA MARTINEZ MORENO . Se obtiene ahora aAde. 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. Por tanto. que es el lado derecho de la producción A → Abc. Elíjase la b más situada a la izquierda y sustitúyase por A.

En este caso escribimos R > S y decimos que R es mas grande que S. la técnica usual es tener la matriz P con los valores: 68 LIC. R y S están ambos en un mango Fig. decimos que R < S o que R es menor que S y S deberá ser la cabeza de alguna regla U → S. entonces saber a que no terminal se reducirá.RS... entonces decimos que no hay relación entre el par ordenado(R. o que R tiene precedencia sobre S. ellos tienen la misma precedencia y deberán ser reducidos al mismo tiempo. Ilustración de las Relaciones de Precedencia. en la gramática. Cuando se usan las relaciones de precedencia para reconocer sentencias..RS... porque deberá ser reducido primero. deben de estar en un mango... Las siguientes tres posibilidades surgirían: R es parte de un mango pero S no Fig.. Si no hay una función sentencial .. Obviamente deberá existir una regla U → .RS. MARTHA MARTINEZ MORENO .(b). Note que debido a que el mango esta a la izquierda de S.S)..R. S deberá ser un terminal..MANUAL DE PROGRAMACIÓN DE SISTEMAS I en cualquier analizador sintáctico ascendente es encontrar el mango. (a).. considerar dos símbolos R y S en el vocabulario V de una gramática G . Note que R deberá ser el símbolo final de alguna regla U → . S es parte de un mango pero R no Fig.. (c). Suponer que hay una forma sentencial . Decimos que R ± S. . lo mejor es usar una forma compacta para representarlas. Dada una forma sentencial X.. Algoritmo de Análisis de Precedencia Simple... En algún punto cualquiera R o S (o ambas).

MARTHA MARTINEZ MORENO .j] = 1 P[i.. Note que la cadena T1. y por lo tanto el mango entero esta en la pila. Cada forma sentencial está encerrada entre los símbolos $ y $ (asumiendo que $ no es un símbolo de la gramática).. Las reglas deben estar en una tabla. Este mango es entonces encontrado en la lista de reglas y reemplazado en la pila. 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. Además se establece que $ < S y S > $ para cualquier símbolo S de la gramática. dado un lado derecho. La sentencia a ser analizada sintácticamente es TiT2.. El proceso se repite hasta que la pila contenga Z y el siguiente símbolo de entrada sea $ (delimitador).MANUAL DE PROGRAMACIÓN DE SISTEMAS I P[i.j] = 2 P[i.j] = 3 si no existe relación entre Si y Sj si Si < Sj si Si = Sj 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. 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.Tn no es una sentencia. entonces el algoritmo parará e indicará esto. hasta que la relación de precedencia > exista entre el símbolo de la cima de la pila y el siguiente símbolo entrante.Tn. su contador es i.j] = 0 P[i. En la Fig. Empezamos con el delimitador de la sentencia $ en la pila y asumimos que ha sido anexada a la sentencia como T[n+1].. j es un índice usado para hacer referencia los elementos en la cima de la pila. 69 LIC. Esto significa que el símbolo de la cima de la pila es el final del mango. Q y R son variables para mantener ciertos símbolos durante el proceso de análisis. estructurada de tal forma que. por el correspondiente no terminal el cual debe ser reducido.

70 LIC. MARTHA MARTINEZ MORENO . Poner el índice de la sentencia apuntando al primer símbolo.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Analizador sintáctico de precedencia simple.7: Si S(i) >R.R) no esta en >. Estos bloques buscan la cabeza del mango. BLOQUE 5 .4: Si (S(i). BLOQUE 2: Obtener el siguiente símbolo. colocarlo en R e incrementar k. el mango deberá estar en la pila. BLOQUE 3 . el mango no esta completamente en la pila. entonces metemos R en la pila y obtenemos el siguiente símbolo. BLOQUE 1: Inicializar la pila S para mantener el delimitador de sentencia ($).

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. Solamente si el mango está en el final derecho de la cadena necesita que la cadena completa esté almacenada.. 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. pero como el mango es reducido. Ejemplo Gramática E →E + T | T T →T * F | F F → ( E ) | id Matriz de precedencia resultante E E T F + * ( ) id = < = = < < > > > > T F + * = > = > > < < < > > ( ) = > > < < < id Análisis Se analizará la cadena siguiente: (x+y) 71 LIC. estos desaparecen.S(i). Si no esta en la parte derecha de una regla. el proceso ha terminado.9 :Checar la cadena S(j).MANUAL DE PROGRAMACIÓN DE SISTEMAS I BLOQUE 8 .. La cadena fue una sentencia si y solamente si 1 = 2 y S(i) = Z. MARTHA MARTINEZ MORENO . Si es la parte derecha de una regla. Los símbolos son leídos uno a la vez de la entrada y almacenados en la pila.

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. 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.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Paso Pila 0 1 2 3 4 5 6 7 8 $ $( $(x $(E $(E+ $ (E+y $ (E+T $(E Relació R Token n < < > = < > > = ( x+y)$ x +y)$ + + y ) ) ) y)$ y)$ )$ $ $ $ $(E) > $ $ Análisis Sintáctico LR. 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. 72 LIC. 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 Leftto-Right). se asume que k es 1. Cuando ésta se omite. MARTHA MARTINEZ MORENO . la “R” por construir una derivación por la derecha (en inglés rightmost derivation) en orden inverso. 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. 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.

un programa conductor y una tabla de análisis sintáctico de dos partes (acción. solo cambian las tablas de un analizador a otro. El programa conductor es el mismo para todos los analizadores sintácticos LR.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. por ejemplo YACC. Consta de una entrada. que indica las transiciones entre los estados. Realización Gramática E →E + T 73 LIC. En la Fig se muestra de forma esquemática de un analizador sintáctico LR. Se necesita una herramienta especializada: un generador de analizadores sintácticos LR. una pila. MARTHA MARTINEZ MORENO . y la función ir_a. Modelo de un analizador sintáctico LR. una salida. ir_a). 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 . la función acción que indica una acción del analizador. La tabla de análisis sintáctico consta de dos partes.

a] = reducir A → β then begin sacar 2*| β | 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] = aceptar then Return else error() end.MANUAL DE PROGRAMACIÓN DE SISTEMAS I E →T T →T * F T →F F →( E ) F → id Algoritmo inicializar la pila al estado cero agregar $ al final de la entrada apuntar al primer símbolo de la entrada repetir 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. end else if acción[s. Matriz de precedencia resultante 74 LIC.a] en la pila. MARTHA MARTINEZ MORENO .

*] = reducir A → β .a] = [5. MARTHA MARTINEZ MORENO .id]. paso 3: 75 LIC. ahora el tope de la pila es 0 y F el lado izquierdo de la producción 6. β = 1 tenemos que sacar 2*|β |. entonces Estado 0 1 2 3 4 5 6 7 id d5 + * Acción ( ) d4 r2 r4 d4 r6 d5 d5 r6 d4 d4 r6 r6 9 3 1 0 $ Aceptar r2 r4 8 2 3 ir_a E T F 1 2 3 d6 r2 d7 r4 r4 d5 8 d6 9 r1 10 r3 11 r5 se mete a (id) y después a siguiente símbolo.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Análisis Inicializa la pila al estado cero.a] = [0. como [S. paso 2: d11 d7 r1 r1 r3 r3 r3 r5 r5 r5 # (5) (el nuevo estado) en la pila. tenemos a S5 que es = desplazar.F. paso 1: id*(id+id) Pila (1) 0 Entrada id*(id+id) $ Acció S a n 0 id Como [S. meter A que es F y el 3 obtenido en la intersección 0. agregar el símbolo “$” al final de la cadena de entrada. avanzar al Verificando en la tabla tenemos (R6).

avanzar al siguiente símbolo. meter A que es E y el 8 obtenido en la intersección 4.(] = desplazar. entonces se mete a(“(“) y después a # (4) (el nuevo estado) en la pila.a] = [4. ahora el tope de la pila es 4 y F el lado izquierdo de la producción 6. ahora el tope de la pila es 4 y E el lado izquierdo de la producción 2.a] = [2. paso 12: 76 LIC.a] = [5. paso 4: Verificando en la tabla tenemos (S7).a] = [3. como [S. como [S. paso 11: Verificando en la tabla tenemos (S5). ahora el tope de la pila es 0 y T el lado izquierdo de la producción 4. β = 1 tenemos que sacar 2*|β |. entonces se mete a(id) y después a # (5) (el nuevo estado) en la pila. como [S. entonces se mete a(+) y después a # (6) (el nuevo estado) en la pila. como [S. meter A que es F y el 3 obtenido en la intersección 4.E. avanzar al siguiente símbolo. avanzar al siguiente símbolo. paso 7: Verificando en la tabla tenemos (R6).T. como [S. β = 1 tenemos que sacar 2*|β |.id] = desplazar. paso 5: Verificando en la tabla tenemos (S4). como [S.F.a] = [7. paso 9: Verificando en la tabla tenemos (R2).+] = reducir A → β .a] = [8. β = 1 tenemos que sacar 2*|β |. paso 10: Verificando en la tabla tenemos (S6). como [S.id] = desplazar. entonces se mete a(*) y después a # (7) (el nuevo estado) en la pila.+] = desplazar.+] = reducir A → β .*] = desplazar. como [S. entonces se mete a(id) y después a # (5) (el nuevo estado) en la pila. avanzar al siguiente símbolo.a] = [2.a] = [6. meter A que es T y el 2 obtenido en la intersección 4.*] = reducir A → β .+] = reducir A → β . MARTHA MARTINEZ MORENO . paso 6: Verificando en la tabla tenemos (S5). β = 1 tenemos que sacar 2*|β |. avanzar al siguiente símbolo.a] = [3. 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 0.T.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Verificando en la tabla tenemos (R4). paso 8: Verificando en la tabla tenemos (R4). como [S.

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

Verificando en la tabla tenemos (R6), como [S,a] = [5,)] = reducir A → β , β = 1 tenemos que sacar 2*|β |, ahora el tope de la pila es 6 y F el 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 → β , β = 1 tenemos que sacar 2*|β |, 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 → β , β = 3 tenemos que sacar 2*|β |, 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 → β , β = 3 tenemos que sacar 2*|β |, 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 → β , β = 3 tenemos que sacar 2*|β |, 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 → β , β = 1 tenemos que sacar 2*|β |, 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 (1) 0 (2) 0 id 5 (3) 0 F 3 (4) 0 T 2 Entrada id*(id+id)$ *(id+id)$ *(id+id)$ *(id+id)$ Acción S5 R6 R4 S7 S 0 5 3 2 a id * * *

77

LIC. MARTHA MARTINEZ MORENO

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

(5) 0 T 2 * 7 (6) 0 T 2 * 7 ( 4 (7) 0 T 2 * 7 ( 4 id 5 (8) 0 T 2 * 7 ( 4 F 3 (9) 0 T 2 * 7 ( 4 T 2 (10) 0 T 2 * 7 ( 4 E 8 (11) 0 T 2 * 7 ( 4 E 8 + 6

(id+id)$ id+id)$ +id)$ +id)$ +id)$ +id)$ )$

S4 S5 R6 R4 R2 S6 S5 R6 R4 R1 S11 R5 R3 R2

7 4 5 3 2 8 6 5 3 9 8 11 10 2 1

( id + + + + id ) ) ) ) ) $ $ $

(12) 0 T 2 * 7 ( 4 E 8 + 6 id )$ 5 (13) 0 T 2 * 7 ( 4 E 8 + 6 F )$ 3 (14) 0 T 2 * 7 ( 4 E 8 + 6 T )$ 9 (15) 0 T 2 * 7 ( 4 E 8 (16) 0 T 2 * 7 ( 4 E 8 ) 11 (17) 0 T 2 * 7 F 10 (18) 0 T 2 (19) 0 E 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> ::= <estado> ::= IF <expr> THEN <estado> ::= <var> := <expr>

78

LIC. MARTHA MARTINEZ MORENO

MANUAL DE PROGRAMACIÓN DE SISTEMAS I

<expr> <var>

::= <expr> + <var> | <var> ::= id $ IF THEN : + id = 1

$ 1 IF IF <expr> THEN <var> := <expr> + id Tabla 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.

79

LIC. MARTHA MARTINEZ MORENO

En cada paso el elemento que se encuentre en la cima de la pila corresponderá a algún renglón de la matriz. En cada paso el elemento en la cima de la pila corresponde a algún renglón de la matriz. 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. ya que en estas podemos almacenar no únicamente símbolos sino también cadenas de símbolos. 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. la cual estará vacía o contendrá el símbolo de la última frase reducida. debido a que ambas representan la cabeza (terminando en un símbolo terminal) de alguna parte derecha.MANUAL DE PROGRAMACIÓN DE SISTEMAS I El analizador sintáctico usa la matriz como sigue. é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. MARTHA MARTINEZ MORENO . 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. Ejemplo: 80 LIC. Realización del Análisis de Matriz de Transición. las subrutinas tendrán la tarea de meter a R en la pila o de llevar a cabo una reducción. se hará uso de una pila S que en cuestión a su estructura es algo diferente a las ya conocidas. Esta subrutina ejecuta la reducción necesaria o mete en la pila a R y obtiene el siguiente símbolo de entrada. Para llevar a cabo el análisis sintáctico y al mismo tiempo construir la matriz.

U= R= $ $ IF IF <expr> THEN <var> := <expr> + id IF 1 THEN : + id = 1 $ Subrutina 1: IF U <> " " then ERROR. después realizaremos la subrutina. 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. el símbolo entrante debe ser IF e id debido a que estas inician partes derechas. para esto se debe de analizar que posible símbolo terminal de la columna podría seguirle al elemento terminal del renglón. S(i) = R . La función SIGCOMPLEX coloca el siguiente símbolo de la cadena de entrada en R. SIGCOMPLEX.MANUAL DE PROGRAMACIÓN DE SISTEMAS I 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. En el renglón IF basándonos en la regla 2. id IF $ 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. i := i + 1 . <expr> le sigue al símbolo IF y como vemos en la regla 4 <expr>:= <var> y en la regla 5 81 LIC. MARTHA MARTINEZ MORENO .

:= ó + son válidos. THEN. basándonos en la regla 2. 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. MARTHA MARTINEZ MORENO . es decir. Valiéndose de estas reglas la matriz al terminar este paso queda: $ $ IF IF <expr> THEN <var> := <expr> + id IF 1 1 THEN : + id = 1 1 1 1 1 como vemos ahora en la cima de la pila se encuentra el símbolo 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.MANUAL DE PROGRAMACIÓN DE SISTEMAS I <var>:= id . En el renglón IF <expr> THEN. que símbolo podría seguirle a id y vemos que los símbolos $. <estado> le sigue al símbolo THEN y como vemos en la misma regla 2 <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. 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 IF <expr> THEN <var> := <expr> + id IF 1 1 THEN : + id = 1 1 1 1 1 2 2 2 2 2 82 LIC.

S(i) = '<expr>'+ SIGCOMPLEX. es decir que símbolo podría seguir a IF pero tomando e cuenta que tenemos a <var> en U. Por lo tanto la matriz quedará: $ IF THEN : + id = $ 1 1 IF 3 1 IF <expr> 1 1 THEN <var> := 3 1 <expr> + 1 id 2 2 2 2 2 Subrutina 3: IF U <> '<expr>' and U <> '<var>'then ERROR. así como también en el renglón <var>:=. i: = i . i := i + 1 . MARTHA MARTINEZ MORENO .1. Vemos que en el renglón IF la única que satisface tal condición es el símbolo + . porque se puede tener una expresión como la siguiente: 83 LIC. THEN ó + son válidos. 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 $. Aquí se hace una reducción y la pila queda: 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. U = '<var>'.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Subrutina 2: IF U <> " " then ERROR. '<expr>+' U = ' <var> ' IF 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.

Se siguen los mismos procedimientos. se hace una parada. Subrutina 5: IF U <> '<prog>' and U <> '<estado>' then ERROR.1 . aunque cabe aclarar que en la subrutina 5.MANUAL DE PROGRAMACIÓN DE SISTEMAS I IF <expr>+<var> THEN <var>:= <expr>+<var>+<var> $ <expr> Por lo tanto la matriz quedará: $ IF THEN : + id = 1 3 1 1 3 1 4 1 2 2 2 +<var> $ 1 IF IF <expr> 1 THEN <var> := <expr> + 4 4 id 2 2 Subrutina 4: IF U <> "<var>" then ERROR. MARTHA MARTINEZ MORENO . U = '<expr>' U = ' <var> ' := '<expr>+' '<expr>' IF R= $ Se hizo aquí otra reducción. i := i . STOP de tal forma que así quedaría la tabla: $ IF THEN : + id = $ 5 1 1 IF 3 1 IF <expr> 1 1 THEN <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 84 LIC.

if U<> '<estado>' then ERROR. if U <> '<var>' and U <> '<expr>' then ERROR. i := i . S (i) := 'IF <expr> THEN'. U := ''. STOP. U:= '<estado>'. if U <> '<var>' and U <> '<expr>' then ERROR. S (i) = '<var> :='.1. U :='<estado>'. i := i . i := i + 1.1. U := ''. SIGCOMPLEX. SIGCOMPLEX. MARTHA MARTINEZ MORENO . 7: 8: 9: 0: 85 LIC. ERROR.MANUAL DE PROGRAMACIÓN DE SISTEMAS I $ IF IF <expr> THEN <var> := <expr> + id 6: 5 0 7 8 4 2 1 0 1 0 0 0 0 9 0 0 4 2 = 6 0 1 0 3 1 6 0 1 0 3 1 0 4 1 2 2 0 if U <> '<var> then ERROR.

err. } 86 LIC. MARTHA MARTINEZ MORENO .write('\n'). import java.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.*.err.util.*. } catch(IOException Ex) { System.println("No se creo el archivo correctamente"). } } public void archSint_escribe(String Mensaje) { try { Sint2.lang. STATIC = false. public FileOutputStream Token1. public class dtone3{ public FileOutputStream Sint1.io.err. import javax. Sint2 = new DataOutputStream(Sint1).swing. } } public void archSint_cerrar() { try { Sint1. Options { LOOKAHEAD = 3. public void archSint_crear(String nombre_arch) { try { Sint1 = new FileOutputStream(nombre_arch).writeBytes(Mensaje+"\r").*. } PARSER_BEGIN(dtone3) import java. public String ar[]=new String[1024].println("No se escribio correctamente"). } catch (IOException Ex) { System. public DataOutputStream Sint2.close(). import java. public DataOutputStream Token2. } catch (IOException Ex) { System. Sint1. IGNORE_CASE = true.println("No se cerro corectamente").*.

ejm\""). analiza. } } public void archtoken_escribe(String Mensaje) { try { Token2.length == 1) { try { analiza = new dtone3 (new java.dtone(). } catch (IOException Ex) { System.length == 0) { System. analiza.io.FileInputStream(args[0])). } } public void archtoken_cerrar() { try { Token1.err.println("No se cerro corectamente").err. MARTHA MARTINEZ MORENO .err.io. } catch(IOException Ex) { System.writeBytes(Mensaje+"\r"). Token1.archSint_crear("Sint.MANUAL DE PROGRAMACIÓN DE SISTEMAS I } public void archtoken_crear(String nombre_arch) { try { Token1 = new FileOutputStream(nombre_arch). } } public static void main(String args[]) throws ParseException { dtone3 analiza.FileNotFoundException e) 87 LIC. } catch (IOException Ex) { System. } catch (java.close().archSint_cerrar(). analiza.err.println("No se creo el archivo correctamente").txt"). Token2 = new DataOutputStream(Token1). if(args.println("Uso del programa: \"java dtone3 archivo.println("No se escribio correctamente"). } else { if(args.write('\n').

io.input(analiza). } try { analiza = new dtone3 (new java.FileInputStream(args[0])). analiza.out.println("No se pudo leer el archivo : " + args[0] + " ").archtoken_crear("Lex. } catch (java.MANUAL DE PROGRAMACIÓN DE SISTEMAS I { System.out.FileNotFoundException e) { System.ejm").txt"). } } } void EscribeSintactico(String mensaje) { archSint_escribe(mensaje).out. MARTHA MARTINEZ MORENO . analiza. } } else { System.println("Formato: java dtone3 nombrearchivo.io. } } 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")> 88 LIC.println("No se pudo leer el archivo : " + args[0] + " ").

"> | <punto: ". MARTHA MARTINEZ MORENO ."> | <dospuntos: ":"> | <coma: ".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: "."> | <comillas: "\""> } TOKEN : { <asigna: "="> | <mayor: ">"> | <menor: "<"> | <menigual: "<="> | <mayigual: ">="> | <suma: "+"> | <resta: "-"> | <div: "/"> | <cambioizq: "<<"> | <cambioder: ">>"> | <producto: "*"> | <residuo: "%"> | <inc: "++"> | <dec: "--"> | <nobin: "~"> | <nolog: "!"> | <igual: "=="> | <dif: "!="> | <ybin: "&"> | <obin: "|"> | <xor: "~|"> | <ylog: "&&"> 89 LIC.

} { t=<paccess> { return ( t.} | t=<pbyte> 90 LIC.image +"\t\t Palabra reservada bool")."A" . analiza.image +"\t\t Palabra reservada access").out. } | t=<pbool> { return ( t.archtoken_escribe(mensaje).") <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."z".println(mensaje)."Z"] > | <entero: (<dig>)+ > | <real: ("-")? <entero> (".} | t=<pbreak> { return ( t. } { ( mensaje=Checa() { System.MANUAL DE PROGRAMACIÓN DE SISTEMAS I | <olog: "||"> | <sumasigna: "+="> | <restasigna: "-="> | <prodasigna: "*="> | <divasigna: "/="> | <resasigna: "%="> | <yasigna: "&="> | <oasigna: "|="> | <xorasigna: "~|="> | <camizqasig: "<<="> | <camderasig: ">>="> } TOKEN : { <dig: ["0"-"9"] > | <letra: ["a" .image +"\t\t Palabra reservada break"). } <EOF> )+ } String Checa() : { Token t. MARTHA MARTINEZ MORENO .

} | t=<pnew> { return ( t. MARTHA MARTINEZ MORENO .} | t=<pif> { return ( t.} | t=<preturn> { return ( t.image +"\t\t Palabra reservada default").image +"\t\t Palabra reservada else").} | t=<pfalse> { return ( t.image +"\t\t Palabra reservada export").image +"\t\t Palabra reservada return").} | t=<pfor> { return ( t.} | t=<pmodule> { return ( t.image +"\t\t Palabra reservada new").image +"\t\t Palabra reservada false").image +"\t\t Palabra reservada module").} | t=<pcase> { return ( t.} | t=<pcontinue> { return ( t.} | t=<pint16> { return ( t.image +"\t\t Palabra reservada if").image +"\t\t Palabra reservada int32").} | t=<pclass> { return ( t.} | t=<pint8> { return ( t.image +"\t\t Palabra reservada case").} | t=<pprivate> { return ( t.image +"\t\t Palabra reservada byte").image +"\t\t Palabra reservada int16").image +"\t\t Palabra reservada class").image +"\t\t Palabra reservada for").image +"\t\t Palabra reservada do").image +"\t\t Palabra reservada int8").} | t=<pdo> { return ( t.image +"\t\t Palabra reservada private").} | t=<pdelete> { return ( t.} | t=<pdefault> { return ( t.} 91 LIC.} | t=<pelse> { return ( t.} | t=<pint32> { return ( t.image +"\t\t Palabra reservada continue").image +"\t\t Palabra reservada delete").MANUAL DE PROGRAMACIÓN DE SISTEMAS I { return ( t.} | t=<pexport> { return ( t.

").image +"\t\t Palabra reservada uint8").} | t=<llaveder> { return ( t.image +"\t\t Simbolo .image +"\t\t Operador ]").image +"\t\t Palabra reservada switch"). MARTHA MARTINEZ MORENO .image +"\t\t Palabra reservada uses").} | t=<punto> { return ( t.} | t=<coma> { return ( t.} | t=<llaveizq> { return ( t.image +"\t\t Palabra reservada uint32").image +"\t\t Simbolo :").} | t=<corizq> { return ( t.image +"\t\t Operador {").image +"\t\t Operador (").} | t=<puint16> { return ( t.").} | t=<corder> { return ( t.image +"\t\t Operador )").image +"\t\t Operador [").} | t=<puint32> { return ( t.} | t=<ptrue> { return ( t.").image +"\t\t Operador = ").} | t=<puses> { return ( t.} | t=<puncoma> { return ( t.MANUAL DE PROGRAMACIÓN DE SISTEMAS I | t=<pswitch> { return ( t.} | t=<dospuntos> { return ( t.} | t=<ptoken> { return ( t.} | t=<pwhile> { return ( t.} | t=<asigna> { return ( t.image +"\t\t Palabra reservada true").} | t=<puint8> { return ( t.image +"\t\t Operador }").image +"\t\t Simbolo .} 92 LIC.image +"\t\t Palabra reservada uint16").} | t=<parender> { return ( t.image +"\t\t Palabra reservada token").} | t=<parenizq> { return ( t.image +"\t\t Palabra reservada while").image +"\t\t Simbolo .

} | t=<producto> { return ( t.image +"\t\t Operador > ").} | t=<nobin> { return ( t.image +"\t\t Operador -.} | t=<cambioizq> { return ( t.image +"\t\t Operador % ").} | t=<dif> { return ( t.} | t=<ybin> { return ( t.} | t=<menor> { return ( t.image +"\t\t Operador ++ ").} | t=<cambioder> { return ( t.image +"\t\t Operador < ").image +"\t\t Operador binario & ").image +"\t\t Operador != "). MARTHA MARTINEZ MORENO .} | t=<suma> { return ( t.image +"\t\t Operador + ").image +"\t\t Operador == ").image +"\t\t Operador / ").} | t=<resta> { return ( t.} | t=<div> { return ( t.image +"\t\t Operador <= ").image +"\t\t Operador << ").image +"\t\t Operador >= ").MANUAL DE PROGRAMACIÓN DE SISTEMAS I | t=<mayor> { return ( t.} | t=<igual> { return ( t.} | t=<inc> { return ( t.").} | t=<dec> { return ( t.} | t=<residuo> { return ( t.} | t=<obin> 93 LIC.} | t=<menigual> { return ( t.image +"\t\t Operador .image +"\t\t Operador >> ").image +"\t\t Operador * ").").image +"\t\t Operador ! ").image +"\t\t Operador ~ ").} | t=<mayigual> { return ( t.} | t=<nolog> { return ( t.

image +"\t\t Operador binario | ").} | t=<ylog> { return ( t.image +"\t\t Letra ").} | t=<sumasigna> { return ( t.image +"\t\t Operador asignacion <<= ").image +"\t\t Operador asignacion >>= ").} | t=<camizqasig> { return ( t.image +"\t\t Operador asignacion |= ").} | t=<real> { return ( t.image +"\t\t Operador binario ~| ").image +"\t\t Operador asignacion += ").} | t=<dig> { return ( t.image +"\t\t Operador asignacion /= ").image +"\t\t Operador asignacion -= ").image +"\t\t Operador asignacion %= ").image +"\t\t Numero Entero ").image +"\t\t Operador asignacion &= ").} | t=<restasigna> { return ( t.image +"\t\t Operador logico || ").} | t=<oasigna> { return ( t.MANUAL DE PROGRAMACIÓN DE SISTEMAS I { return ( t.image +"\t\t Identificador ").} | t=<entero> { return ( t.} 94 LIC.} | t=<ident> { return ( t.} | t=<letra> { return ( t.} | t=<xorasigna> { return ( t.image +"\t\t Numero real ").image +"\t\t Digito ").image +"\t\t Operador asignacion ~|= ").} | t=<prodasigna> { return ( t.} | t=<yasigna> { return ( t. MARTHA MARTINEZ MORENO .} | t=<camderasig> { return ( t.} | t=<resasigna> { return ( t.} | t=<divasigna> { return ( t.} | t=<xor> { return ( t.image +"\t\t Operador asignacion *= ").image +"\t\t Operador logico && ").} | t=<olog> { return ( t.

image +"\t\t Caracter no valido "). void dtone() : {} { try{ CABECERA() CUERPO() } catch(ParseException e) {System.out. MARTHA MARTINEZ MORENO . do{ t = getNextToken().MANUAL DE PROGRAMACIÓN DE SISTEMAS I | t=<c_noval> { return ( t. } } 95 LIC.kind != EOF). Se describen las gramáticas y se da una breve descripción de cada una de ellas.out.image +"\t\t Cadena no valida "). Esta gramática corresponde a la estructura general de todo el programa. } } Esta es la gramática que corresponde a la versión que va en la cabecera.out. Token t. void VERSION() : {} {try {<pdtone> <dig> <punto> <dig> <punto> <dig> } catch(ParseException e) { System. La versión es muy útil para identificar la versión del compilador que este código necesita.println("Error en la version").} } EJEMPLO DE ANALISIS SINTÁCTICO DEL LENGUAJE DTONE Este es el código en Java del Analizador Sintáctico.println("Error en el programa"). Identifica el archivo como código del dtone.kind != puncoma & t.println("Error en la cabecera").} while(t.} | t=<cad_noval> { return ( t. Éste tiene que ser el primer comando en todos los archivos. void CABECERA() : {} {try {VERSION() LIBRERIAS() } catch(ParseException e) { System. } } Esta es la gramática que corresponde a la cabecera del programa.

en el se van a listar las instrucciones para del programa.kind != puncoma & t.kind != EOF). } } Esta es la gramática en la cual se define el cuerpo del programa. void LIBRERIAS() : {} {try {(<puses> <comillas> <ident>("/" <ident>)* "/*" <comillas> <puncoma>)+ } catch(ParseException e) { System. do{ t = getNextToken(). } } Esta es la gramática de la declaración de variables. MARTHA MARTINEZ MORENO .kind != puncoma & t.println("Error en las declaraciones").} while(t. do{ t = getNextToken(). void CUERPO() : {} {try {<pmodule> <ident> <llaveizq> DECLARA() (INSTRUC())+ <llaveder> } catch(ParseException e) { System. } } 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.kind != puncoma & t. Token t. void D_VAR() : {} {try {(DEC_VAR() | DECINI_VAR()) <puncoma> } catch(ParseException e) { 96 LIC.} while(t.out.kind != EOF).kind != EOF).MANUAL DE PROGRAMACIÓN DE SISTEMAS I Esta es la gramática que corresponde a las librerías.} while(t. Token t.out.println("Error en el cuerpo"). lo que se hace es que se listan las librerías que van a ser llamadas para la ejecución del programa. do{ t = getNextToken().println("Error en las librerias"). Token t.out.

out. void TIPO() : {} { (<pint8> | <pint16> | <pint32> | <puint8> | <puint16> | <puint32> | <pbool> | <pbyte>) } Esta gramática corresponde a la declaración e inicialización de variables. void D_ARR() : {} {try {TIPO() <ident> <corizq> (<entero>)* <corder> <puncoma> } catch(ParseException e) { System. Token t. void DECINI_VAR() : {} {try {DEC_VAR() <igual> (<entero> | <real> | <cadena>) <puncoma> } catch(ParseException e) { System.kind != EOF).} while(t. 97 LIC.kind != puncoma & t. MARTHA MARTINEZ MORENO .kind != EOF).} while(t.println("Error en las declaracion de variable").println("Error en las declaracion de variable"). do{ t = getNextToken(). } } Esta gramática corresponde a la declaración de un arreglo. Token t.} while(t. void DEC_VAR() : {} {try {TIPO() <ident> } catch(ParseException e) { System.println("Error en las declaracion de arreglo"). Token t.MANUAL DE PROGRAMACIÓN DE SISTEMAS I System. do{ t = getNextToken().out. do{ t = getNextToken().out.kind != EOF). } } Esta gramática corrsponde al tipo de datos con el cual se va a declarar la variable.out.kind != puncoma & t. } } Esta es la gramática de la declaración de variables.println("Error en las declaracion de variable").kind != puncoma & t.

} while(t. MARTHA MARTINEZ MORENO .println("Error en las declaracion de constante"). void D_CONST() : {} {try {<pdefine> <ident> <igual> (<entero>|<real>|<cadena>) <puncoma> } catch(ParseException e) { System. Token t.println("Error en la declaracion de la funcion").kind != puncoma & t.kind != puncoma & t.out.kind != EOF).kind != EOF).MANUAL DE PROGRAMACIÓN DE SISTEMAS I Token t.out.kind != puncoma & t.out. void PARAM() : {} {try {(DEC_VAR() | D_ARR()) (<coma> (DEC_VAR() | D_ARR() ) )* } catch(ParseException e) { System. void D_FUNCION() : {} {try {TIPO() <ident> <parenizq> PARAM() <parender> <llaveizq> DECLARA() (INSTRUC())+ < llaveder > } catch(ParseException e) { System.} while(t. } } 98 LIC. Token t. do{ t = getNextToken().println("Error en los parametros"). } } Esta gramática corresponde a la declaración de parámetros del programa. } } Esta gramática corresponde a la declaración de una constante. } } Esta gramática corresponde a la declaración de una función.kind != EOF).} while(t. do{ t = getNextToken(). Token t. do{ t = getNextToken().kind != puncoma & t. do{ t = getNextToken().} while(t.kind != EOF).

} } Esta es la gramática que define las asignaciones que podemos hacer en el lenguaje de programación Dtone.} while(t.out.} while(t. void ASIGNACION() : {} {try {IZQ() <asigna> DER() <puncoma> } catch(ParseException e) { System. do{ t = getNextToken().println("Error en la parte izquierda de la asginacion").kind != EOF).kind != puncoma & t.kind != puncoma & t.println("Error en la instruccion"). do{ t = getNextToken(). Token t.out. void ARREGLO() : {} {try {<ident> <corizq> (<ident>|<entero>) (<coma> (<ident>|<entero>))* <corder> 99 LIC. MARTHA MARTINEZ MORENO . Token t.} while(t.kind != EOF).out. } } Esta es la gramática que define la parte izquierda de las asignaciones que podemos hacer en el lenguaje de programación Dtone. Token t.kind != EOF). es decir al arreglo se le va a asignar un valor. } } Esta gramática define al arreglo que puede ir en la parte izquierda de la asignación. do{ t = getNextToken().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.kind != puncoma & t. void IZQ() : {} {try {(<ident> | ARREGLO()) } catch(ParseException e) { System. void INSTRUC() : {} {try {(ASIGNACION() | CICLO() | LLAM_FUNCION()) } catch(ParseException e) { System.println("Error en la asignacion").

} while(t.println("Error en la operacion").out.kind != puncoma & t.} while(t. do{ t = getNextToken(). void DER() : {} {try {(IZQ() | <cadena> | OPER_ARIT() | <entero> | <real>) } catch(ParseException e) { System.kind != puncoma & t.MANUAL DE PROGRAMACIÓN DE SISTEMAS I } catch(ParseException e) { System. } } Esta es la gramática que define la parte derecha de las asignaciones que podemos hacer en el lenguaje de programación Dtone. void ARITOP() : {} { (<suma> | <resta> | <div> | <producto> | <residuo>) } Esta gramática define los ciclos que puede contener el cuerpo del programa en el lenguaje Dtone. Token t. Token t. void CICLO() : {} 100 LIC. do{ t = getNextToken().kind != EOF).println("Error en el arreglo"). } } Esta gramática corresponde a los operadores aritméticos que pueden ir en la parte derecha de una asignación. Token t. void OPER_ARIT() : {} {try {(<ident> | <entero> | <real> | ARREGLO()) ARITOP() (<ident> | <entero> | <real> | ARREGLO()) } catch(ParseException e) { System.out.kind != EOF).println("Error en la parte derecha de la asignacion"). } } Esta gramática define el simbolo del operador aritmético. do{ t = getNextToken().kind != puncoma & t.kind != EOF). MARTHA MARTINEZ MORENO .} while(t.out.

kind != EOF). do{ t = getNextToken(). void GIF() : {} {try {<pif> <parenizq> OPER_REL() <parender> (INSTRUC())+ } catch(ParseException e) { System. void OPER_REL() : {} {try {(<ident> | <entero> | <real> | ARREGLO()) RELOP() (<ident> | <entero> | <real> | ARREGLO()) } catch(ParseException e) { System. En este ciclo se ejecuta el cuerpo si la condición es verdadera. en caso contrario no. Token t. MARTHA MARTINEZ MORENO . la verdadera trayectoria se ejecuta.} while(t. si no la falsa trayectoria (opcional) se ejecuta.println("Error en el IF"). Se evalúa la condición.out. } } 101 LIC.println("Error en el WHILE"). void GWHILE() : {} {try {<pwhile> <parenizq> OPER_REL() <parender> <llaveizq> (INSTRUC())+ <llaveder> } catch(ParseException e) { System. } } Esta es la gramática 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.out.kind != puncoma & t. si produce una verdad.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.MANUAL DE PROGRAMACIÓN DE SISTEMAS I { (GIF() | GWHILE() | GDO() | GFOR() | GCASE()) } Esta es la gramática del IF.

out.println("Error en el DO").println("Error en el FOR").} while(t.} while(t. void GCASE() : {} {try {<pswitch> <parenizq> (INSTRUC())+ <parender> <llaveizq> (<pcase> <entero> <dospuntos> (INSTRUC()) +)+ (<pdefault> <dospuntos> (INSTRUC())+)? <llaveder> } catch(ParseException e) { System.println("Error en el CASE"). el cuerpo es ejecutado mientras que la condición sea cierta.kind != puncoma & t.kind != EOF). En este ciclo.} while(t. void GFOR() : {} {try {<pfor> <parenizq> (DECINI_VAR() | ASIGNACION()) <puncoma> OPER_REL() <puncoma> ASIGNACION() <parender> <llaveizq> (INSTRUC())+ <llaveder> } catch(ParseException e) { System. do{ t = getNextToken(). } } Esta gramática corresponde al case. } } La siguiente gramática describe la llamada a una función en Dtone.kind != EOF). Esta estructura no requiere declaraciones de ruptura. MARTHA MARTINEZ MORENO . El cual tiene una estructura muy parecida a la de C. void GDO() : {} {try {<pdo> <llaveizq> (INSTRUC())+ <llaveder> <pwhile> <parenizq> OPER_REL() <parender> <puncoma> } catch(ParseException e) { System. Token t.out.kind != EOF).kind != puncoma & t. Token t.out. El cuerpo del ciclo se ejecuta por lo menos una vez. do{ t = getNextToken(). Token t. } } Esta es la gramática del ciclo FOR. do{ t = getNextToken().MANUAL DE PROGRAMACIÓN DE SISTEMAS I Esta es la gramática del ciclo DO. void LLAM_FUNCION() : {} {try 102 LIC.kind != puncoma & t.

MARTHA MARTINEZ MORENO .MANUAL DE PROGRAMACIÓN DE SISTEMAS I {<ident> <parenizq> (<ident> | <entero> | <cadena> | <real>)* <parender> <puncoma> } catch(ParseException e) { System.kind != puncoma & t.println("Error en la llamada").} while(t. do{ t = getNextToken().out. Token t. } } 103 LIC.kind != EOF).

Sign up to vote on this title
UsefulNot useful