P. 1
Manual Completo de Programacion de Sistemas I

Manual Completo de Programacion de Sistemas I

|Views: 1.618|Likes:
Publicado porzjulca

More info:

Published by: zjulca on Jun 18, 2011
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as DOC, PDF, TXT or read online from Scribd
See more
See less

06/19/2013

pdf

text

original

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

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

/.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 +.*. Todas las computadoras tiene un código de 5 LIC. lenguajes ensambladores y lenguajes de alto nivel.MOD AND NOT OR < > > < = = := DELIMITADORES OPERACIONALES RELACIONALES ASIGNACION PALABRAS RESERVADAS CICLOS DECLARACION DE VARIABLES DECLARACION DE CONSTANTES No obstante. aunque todos los lenguajes de programación tienen un conjunto de instrucciones que permiten realizar dichas operaciones. Lenguaje de máquina El lenguaje de máquina de una computadora consta de cadenas de números binarios (ceros y unos) y es el único que "entienden" directamente los procesadores. Todas las instrucciones preparadas en cualquier lenguaje de máquina tienen por lo menos dos partes. MARTHA MARTINEZ MORENO .-. que dice a la computadora cuál es la función que va a realizar. La primera es el comando u operación. existe una marcada diferencia en los símbolos. caracteres y síntaxis de los lenguajes de máquina.

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

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

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

Impuros: aquellos que poseen evaluación ansiosa. es decir. Es justamente. las expresiones no se evalúan hasta que no se necesitan los resultados. Vistas las características fundamentales de los lenguajes funcionales. Existencia de un sistema de tipos fuerte Uso del Patter Matching: comprobación de patrones. Estas son las únicas estructuras de control. MARTHA MARTINEZ MORENO . 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.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Evaluación perezosa (o lazy). Posee evaluación perezosa. lo contrario de la evaluación eager(ansiosa). La evaluación perezosa nos proporciona la ventaja de trabajar con listas potencialmente infinitas. estos pueden clasificarse en dos tipos: Puros: aquellos en los que sólo podemos aplicar funciones. donde la evaluación de los argumentos se realiza antes de aplicar la función.

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

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

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

generada por el analizador léxico.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. MARTHA MARTINEZ MORENO . que es representada en forma de un árbol sintáctico. 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.

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

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

un mismo programa necesita ejecutarse en diferentes posiciones de memoria. ala acción de motor se le llama vulgarmente “Lindar”. • • 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. es decir. 18 LIC. con reubicación y ligadores. unos datos almacenados en un periférico de memoria externa (cintas. Entre los programas que se manejan en la programación de sistemas se encuentran: los sistemas operativos.) sirven para cargar con la memoria pequeños programas que inician el funcionamiento de la computadora. Un cargador es un programa que coloca en la memoria para su ejecución. Si dichas instrucciones se almacenan siempre en el mismo espacio de memoria (cada vez que se ejecute el programa cargador). MARTHA MARTINEZ MORENO . el programa cargador pone en memoria las instrucciones guardadas en sistemas externos. Cargadores con publicación: en ocasiones. absolutos. los compiladores. discos. los manejadores de base de datos. etc. • 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. Dependiendo de la manera en que se manejan los procesos de ligas y carga. se dice que es un cargador absoluto. Cargadores Absolutos: como ya se menciono. el programa guardado en algún dispositivo de almacenamiento secundario. la introducción debe realizarse de forma adecuada.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. • Cargadores Iniciales: son aquellos que indican ala PC la forma de poner en memoria principal. Para esto.

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

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

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

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

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

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

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

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

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

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. 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. Estos primeros lenguajes también vinieron acompañados de un nuevo término: Compilador. 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. Esto lo podemos apreciar desde finales de la década de los 50's. limitando la intervención del operador solo al montaje de cintas y discos. 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. MARTHA MARTINEZ MORENO . cuando con el advenimiento de computadoras comerciales surge también la necesidad de programarlas. cuando 28 LIC.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. 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. En este caso. Por otra parte se tienen los sistemas de tiempo compartido (multiusuario) que proporcionan acceso interactivo o conversacional a varios usuarios. 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. cada una de ellas probada individualmente.

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

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

MARTHA MARTINEZ MORENO . UNIDAD III ANÁLISIS LÉXICO EL ANALIZADOR LÉXICO 31 LIC. • Descompilador: es un programa que acepta como entrada código máquina y lo traduce a un lenguaje de alto nivel. desarrollados para UNIX. realizando el proceso inverso a la compilación.MANUAL DE PROGRAMACIÓN DE SISTEMAS I léxicos y sintácticos. Los inconvenientes que tienen son que los analizadores que generan no son muy eficientes.

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

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

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

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

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

23. una serie de normas que describen todas las cadenas que corresponden al mismo token. Se dice que el patrón concuerda con el lexema que cumple ese patrón. y ese es el que marca el token. El patrón de las primeras es la simple enumeración de los lexemas. De este modo. de las segundas. Por ejemplo.0.MANUAL DE PROGRAMACIÓN DE SISTEMAS I  Las definidas por el programador (bajo ciertas normas genéricas): identificadores. literales. pepe 34. una vez delimitado el lexema. A77aH. 23442. seguido opcionalmente de 'E' o 'e' y dígitos 37 LIC.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. 1 34. Es decir. El patrón permite identificar el token y asignar el atributo (suponiendo delimitado el lexema). 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. comentarios. Normalmente. el número "posible" de lexemas es infinito. 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. cumplirá algún patrón. MARTHA MARTINEZ MORENO .

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

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

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

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

Por ejemplo: La herramienta Lex. MARTHA MARTINEZ MORENO .MANUAL DE PROGRAMACIÓN DE SISTEMAS I a) Utilizar un generador de analizador léxico automático. 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. 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"). que proporciona rutinas para leer la entrada y generar componentes léxicos.

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

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

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

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

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

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

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. 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.MANUAL DE PROGRAMACIÓN DE SISTEMAS I El conjunto de los lenguajes sensibles al contexto contiene el conjunto de los lenguajes independientes del contexto.1. tal como se muestra en al Tabla 3. lenguajes libres de contexto y lenguajes 49 LIC. podemos deducir de la definición que ∉ L(G). MARTHA MARTINEZ MORENO . para cualquier gramática que es  sensible al contexto. A veces es conveniente que la cadena pertenezca al lenguaje que ha sido generado por una gramática sensible al contexto. Noam Chomsky clasificó las gramáticas en cuatro tipos de lenguajes y esta clasificación es conocida como la jerarquía de Chomsky. Clasificación de las Gramáticas. lenguajes sensibles al contexto. en la cual cada lenguaje es descrito por el tipo de gramática generado. Los cuatro tipos son: lenguajes recursivamente enumerables. Estos lenguajes sirven como base para la clasificación de lenguajes de programación. Puesto que la cadena vacía tiene longitud cero. En 1959.

Con esto se impide que las cadenas que se obtengan en el transcurso de la aplicación de las reglas sean de longitud decreciente.1. se pueden definir varias gramáticas con diferentes niveles de restricción. MARTHA MARTINEZ MORENO . porque del lado izquierdo puede haber más de un elemento. Dichos lenguajes también se identifican como lenguajes de tipo 0. Las gramáticas sensibles al contexto reciben el nombre de gramáticas de tipo 1. 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. lo que implica que un símbolo puede reemplazarse en el contexto de otros. 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. 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. y se impide así mismo el caso extremo de que mediante una gramática de tipo1 puedan desaparecer cadenas de símbolo.2 y 3.MANUAL DE PROGRAMACIÓN DE SISTEMAS I regulares. 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.

MANUAL DE PROGRAMACIÓN DE SISTEMAS I Finitos y Expresiones Regulares Los cuatro tipos de gramáticas. el conjunto de lenguajes recursivamente enumerables contiene propiamente al conjunto de lenguajes recursivos que contiene propiamente al conjunto de lenguajes sensibles al contexto que contiene propiamente al conjunto de lenguajes libres de contexto. que a su vez contiene propiamente a los lenguajes regulares. Regulares Sobre un alfabeto dado. tal como se muestra: Gramáticas Ambiguas 51 LIC. MARTHA MARTINEZ MORENO .

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

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

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

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

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

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

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

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

60 LIC. 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 . 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. Un analizador sintáctico predictivo. Id := Expresión Termino Expresión' + Termino Expresión' | ε Factor Termino' * Factor Termino' | ε ( Expresión ) Id Literal Análisis sintáctico Predictivo. Es una forma eficiente de implementar el análisis sintáctico descendente implícitamente manteniendo una pila. Un analizador sintáctico es guiado por una tabla en la que se encuentran las producciones. MARTHA MARTINEZ MORENO . El modelo sintáctico predictivo puede verse en la figura de abajo. a cada símbolo no terminal de una gramática se le asocia con una producción.

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

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

así que tenemos en la pila a "$E". y reemplazamos X = E por su producción la cual es TE'. Introduciremos la 63 LIC. ocupamos la regla 3 del algoritmo. 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. y la salida será la producción del no terminal "E". MARTHA MARTINEZ MORENO . 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 "$". y reemplazamos X = T por su producción la cual es FT'. y tendremos en la pila "$ E' T". Paso 3 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 donde S es el símbolo de inicio dela gramática y w es la cadena que ha de ser analizada. Y en la entrada tenemos a la cadena completa. Introduciremos la producción en el orden inverso. ocupamos la regla 3 del algoritmo.

y la salida será la producción de "F". Paso 9 En la cima de la pila tenemos a " T " verificamos en la gramática y tenemos que es un no terminal. ocupamos la regla 3 del algoritmo. Introduciremos la producción en el orden inverso. y tendremos en la pila "$ E' T +" y la salida será la producción del no terminal " E' ". MARTHA MARTINEZ MORENO . y tendremos en la pila "$ E' T' F" y la salida será la producción del no terminal " T ". Paso 4 Como la cima de la pila esta X= F y este produce el símbolo de la cadena leída. Paso 10 Como la cima de la pila esta X= F y este produce el símbolo de la cadena leída. y tendremos en la pila "$ E' T' F". ocupamos la regla 3 del algoritmo. ocupamos la regla 3 del algoritmo.MANUAL DE PROGRAMACIÓN DE SISTEMAS I producción en el orden inverso. y reemplazamos X = E' por su producción la cual es +TE'. 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' ξ ". se sustituye por su producción que es el terminal "id" y 64 LIC. y reemplazamos X = T' por su producción la cual es ξ . 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 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 6 En la cima de la pila tenemos a "T'" verificamos en la gramática y tenemos que es un no terminal. 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. Introduciremos la producción en el orden inverso. ahora tendremos en la pila "$ E' T' id". y la salida será la producción del no terminal "T". Paso 7 En la cima de la pila tenemos a " E' " verificamos en la gramática y tenemos que es un no terminal.

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'. Paso 14 Como la cima de la pila esta X= F y este produce el símbolo de la cadena leída. ocupamos la regla 3 del algoritmo. y tendremos en la pila "$ E'" ó "$ E' ξ " y la salida será la producción del no terminal " T' ". ocupamos la regla 3 del algoritmo. ahora tendremos en la pila "$ E' T' id". Paso 17 En la cima de la pila tenemos a " E' " verificamos en la gramática y tenemos que es un no terminal. ahora tendremos en la pila "$ E' T' id". Paso 12 En la cima de la pila tenemos a " T' " verificamos en la gramática y tenemos que es un no terminal. 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. Introduciremos la producción en el orden inverso. y la salida será la producción de "F". y reemplazamos X = E' por su producción la cual es ξ . Introduciremos la 65 LIC. y la salida será la producción de "F". y reemplazamos X = T' por su producción la cual es ξ . y tendremos en la pila "$ E' T' F *" y la salida será la producción del no terminal " T' ". ocupamos la regla 3 del algoritmo. Introduciremos la producción en el orden inverso. MARTHA MARTINEZ MORENO . 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 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. se sustituye por su producción que es el terminal "id" y avanza el apuntador de entrada al siguiente símbolo de la cadena.

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). MÉTODOS ASCENDENTES O BOTTOM UP.MANUAL DE PROGRAMACIÓN DE SISTEMAS I producción en el orden inverso. 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. y tendremos en la pila "$ E'" ó "$ E' ξ " y la salida será la producción del no terminal " T' ". 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. En cada paso de reducción se sustituye una subcadena 66 LIC. MARTHA MARTINEZ MORENO .

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

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

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

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

Una ventaja acerca de este y otros analizadores sintácticos similares es que la cadena completa de los símbolos de entrada no necesitan estar en memoria al mismo tiempo. Los símbolos son leídos uno a la vez de la entrada y almacenados en la pila.S(i).MANUAL DE PROGRAMACIÓN DE SISTEMAS I BLOQUE 8 . Solamente si el mango está en el final derecho de la cadena necesita que la cadena completa esté almacenada. Si no esta en la parte derecha de una regla. pero como el mango es reducido. MARTHA MARTINEZ MORENO .. Si es la parte derecha de una regla..9 :Checar la cadena S(j). borramos el mango de la pila (bloque 9). La cadena fue una sentencia si y solamente si 1 = 2 y S(i) = Z. 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. el proceso ha terminado. estos desaparecen. metemos en la pila el símbolo el cual es reducido y regresamos al bloque 3 para buscar el siguiente mango.

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. 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. Cuando ésta se omite. MARTHA MARTINEZ MORENO . 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. 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. 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. 72 LIC. la “R” por construir una derivación por la derecha (en inglés rightmost derivation) en orden inverso. se asume que k es 1. Esta técnica de análisis sintáctico resulta conveniente por varias razones: Se puede construir analizadores sintácticos LR para reconocer prácticamente todas las construcciones de los lenguajes de programación para los que se pueden escribir gramáticas libres de contexto. El 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).

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

a] = desplazar # then begin meter a y después # (el nuevo estado) en la pila avanzar al siguiente símbolo de entrada end else if acción [s.a] = reducir A → β 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] en la pila. end else if acción[s.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. Matriz de precedencia resultante 74 LIC. MARTHA MARTINEZ MORENO .a] = aceptar then Return else error() end.

β = 1 tenemos que sacar 2*|β |. ahora el tope de la pila es 0 y F el lado izquierdo de la producción 6. como [S. MARTHA MARTINEZ MORENO . paso 1: id*(id+id) Pila (1) 0 Entrada id*(id+id) $ Acció S a n 0 id Como [S. avanzar al Verificando en la tabla tenemos (R6). 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. paso 3: 75 LIC.F.a] = [5.*] = reducir A → β . paso 2: d11 d7 r1 r1 r3 r3 r3 r5 r5 r5 # (5) (el nuevo estado) en la pila.MANUAL DE PROGRAMACIÓN DE SISTEMAS I Análisis Inicializa la pila al estado cero.a] = [0.id]. tenemos a S5 que es = desplazar. 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.

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

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

se hará uso de una pila S que en cuestión a su estructura es algo diferente a las ya conocidas. 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 . En cada paso el elemento en la cima de la pila corresponde a algún renglón de la matriz. las subrutinas tendrán la tarea de meter a R en la pila o de llevar a cabo una reducción. la cual estará vacía o contendrá el símbolo de la última frase reducida. 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. Estas dos juntas determinan un elemento de la matriz el cual es el número de subrutina a ejecutar. Ejemplo: 80 LIC. 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. En cada paso el elemento que se encuentre en la cima de la pila corresponderá a algún renglón de la matriz. 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. Realización del Análisis de Matriz de Transición. El símbolo terminal entrante determina una columna de la matriz.

el símbolo entrante debe ser IF e id debido a que estas inician partes derechas. <expr> le sigue al símbolo IF y como vemos en la regla 4 <expr>:= <var> y en la regla 5 81 LIC. 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 . 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. después realizaremos la subrutina. SIGCOMPLEX. 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. de acuerdo a la gramática que usamos en este ejemplo. i := i + 1 . En el renglón IF basándonos en la regla 2. MARTHA MARTINEZ MORENO .MANUAL DE PROGRAMACIÓN DE SISTEMAS I Cuando iniciamos el análisis $ esta en la pila y la variable U está vacía. U= R= $ $ IF IF <expr> THEN <var> := <expr> + id IF 1 THEN : + id = 1 $ Subrutina 1: IF U <> " " then ERROR. La función SIGCOMPLEX coloca el siguiente símbolo de la cadena de entrada en R.

THEN.MANUAL DE PROGRAMACIÓN DE SISTEMAS I <var>:= id . 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. <estado> le sigue al símbolo THEN y como vemos en la misma regla 2 <estado>:= IF <expr> THEN <estado>. := ó + son válidos. En el renglón IF <expr> THEN. 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 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. basándonos en la regla 2. 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. MARTHA MARTINEZ MORENO . que símbolo podría seguirle a id y vemos que los símbolos $. 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. es decir. 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.

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. es decir que símbolo podría seguir a IF pero tomando e cuenta que tenemos a <var> en U. THEN ó + son válidos. así como también en el renglón <var>:=. i: = i . porque se puede tener una expresión como la siguiente: 83 LIC. S(i) = '<expr>'+ SIGCOMPLEX. i := i + 1 . 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. 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>'. Vemos que en el renglón IF la única que satisface tal condición es el símbolo + .1. MARTHA MARTINEZ MORENO .

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.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. aunque cabe aclarar que en la subrutina 5.1 . se hace una parada. Subrutina 5: IF U <> '<prog>' and U <> '<estado>' then ERROR. Se siguen los mismos procedimientos. i := i . MARTHA MARTINEZ MORENO . U = '<expr>' U = ' <var> ' := '<expr>+' '<expr>' IF R= $ Se hizo aquí otra reducción.

if U <> '<var>' and U <> '<expr>' then ERROR. SIGCOMPLEX. i := i + 1.1. i := i . STOP. i := i .1. if U<> '<estado>' then ERROR. 7: 8: 9: 0: 85 LIC.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. S (i) := 'IF <expr> THEN'. U := ''. ERROR. S (i) = '<var> :='. MARTHA MARTINEZ MORENO . U := ''. if U <> '<var>' and U <> '<expr>' then ERROR. U:= '<estado>'. U :='<estado>'. SIGCOMPLEX.

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

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

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

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

out. MARTHA MARTINEZ MORENO .") <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. analiza.image +"\t\t Palabra reservada break"). } { ( mensaje=Checa() { System. } { t=<paccess> { return ( t."A" .image +"\t\t Palabra reservada access"). } | t=<pbool> { return ( t.} | t=<pbreak> { return ( t. } <EOF> )+ } String Checa() : { Token t."z".} | t=<pbyte> 90 LIC.archtoken_escribe(mensaje)."Z"] > | <entero: (<dig>)+ > | <real: ("-")? <entero> (".println(mensaje).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 bool").

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

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

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

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

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

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

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

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

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

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

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

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

} } 103 LIC.kind != EOF).} while(t. Token t.println("Error en la llamada").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. do{ t = getNextToken().out.

You're Reading a Free Preview

Descarga
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->