Académique Documents
Professionnel Documents
Culture Documents
INSTITUTO TECNOLGICO SUPERIOR DE JESS CARRANZA ING. EN SISTEMAS COMPUTACIONALES GRUPO 502-A MARCOS LPEZ PASCUAL
INDICE
INTRODUCCION ........................................................................................................................................... 3
CONCLUSION ............................................................................................................................................. 23
INTRODUCCION
La habilidad para analizar un programa, razonando acerca de sus propiedades, es una de las tareas ms importantes en el diseo de software y en la manipulacin de programas. El anlisis de flujo de datos, es decir, el proceso de recoger informacin sobre la forma en que el programa usa las variables y las estructuras de datos (sin necesidad de ejecutarlo) juega un papel fundamental en el diseo de programas que, a su vez, transforman programas (como compiladores, intrpretes, sistemas de comprobacin de tipos, etc). Esto queda justificado, por ejemplo, cuando se recapacita sobre la enorme proporcin de cdigo dedicada en la mayora de compiladores modernos a la comprobacin y optimizacin del cdigo generado, puesto que cualquier mejora en el cdigo intermedio contribuye a producir un cdigo mquina correcto (con idntica semntica) y ms rpido. Una aproximacin interesante para el anlisis de los lenguajes de alto nivel consiste en considerar el anlisis del programa como un tipo de pseudo evaluacin, es decir, un proceso que imita la ejecucin del programa. La teora de la Interpretacin Abstracta permite el diseo y verificacin sistemtica de anlisis de flujo de datos, formalizando la
relacin entre anlisis y semntica. Diferentes estilos de definicin semntica conducen a diferentes aproximaciones al anlisis de programas.
De la instruccin declarativa, la tabla de smbolos y el analizador morfolgico obtenemos los atributos de los operandos:
/ --------| | a + int --------| | b ^ int --------| | c 2 int int
El rbol sintctico sera el mismo, sustituyendo 2 por -2. Sin embargo, la propagacin de atributos sera diferente:
/ real --------| | a + real int --------| | b ^ real int --------| | c -2 int int
En algn caso podra llegar a producirse error (p.e. si / representara slo la divisin entera). Si la expresin hubiera sido
int a,b,c,d; a/(b+c^d)
El rbol sintctico sera el mismo, sustituyendo 2 por d. Sin embargo, la propagacin de atributos sera incompleta:
/ {int,real} --------| | a + {int,real} int --------| | b ^ {int,real} int --------| | c d int int
El analizador semntico podra reducir los tipos inseguros al tipo mximo (real) o utilizar un tipo interno nuevo (ej. arit={int,real}, una unin). Lo anterior es un ejemplo de propagacin bottom-up. La propagacin top-down tambin es posible: lo que se transmite son las restricciones y los tipos de las hojas sirven de comprobacin. Por ejemplo, si la divisin slo puede ser entera, transmitimos hacia abajo la restriccin de que sus operandos slo pueden ser enteros. Al llegar a d, esa restriccin se convierte en que d debe ser positiva. Si no lo es, error.
La implantacin de todos los casos posibles de operacin con tipos mixtos podra ser excesivamente cara. En su lugar, se parte de operaciones relativamente simples (ej. int+int, real+real) y no se implementan las restantes (ej. int+real, real+int), aadiendo en su lugar operaciones mondicas de cambio de tipo (ej. int->real). Esta decisin puede introducir ambigedades. Por ejemplo, sea el programa
real a; int b,c; a:=b+c
El problema es que no tenemos garanta de que los dos procedimientos sean equivalentes. El segundo puede dar overflow, el primero prdida de precisin. La definicin del lenguaje debe especificar estos casos. Las transformaciones posibles se pueden representar mediante un grafo cuyos nodos son los tipos de datos y cada arco indica una transformacin. Dado un operando de tipo A que se desea convertir al tipo B, se trata de encontrar una cadena de arcos que pase de A a B en el grafo anterior. Podra haber varios grafos, cada uno de los cuales se aplicar en diferentes condiciones, por ejemplo, uno para las asignaciones, otro para las expresiones, etc.
Equivalencias de expresiones de tipo En el apartado anterior hemos utilizado la funcin typeEqual (t1,t2: TypeExp) : boolean. Todo verificador de tipos debe saber cuando dos expresiones de tipo representan al mismo tipo, y de eso es de lo que se encarga esta funcin. La equivalencia de tipos se puede explicar haciendo uso de los conceptos de equivalencia estructural y equivalencia de nombre. El anlisis se hace mediante una representacin de grafos de las expresiones de tipos, con hojas para los tipos bsicos y nodos interiores para los constructores de tipo. Ejemplo; La expresin de tipo: Record X: pointer to real Y: array [10] of in End Esta expresin se puede representar mediante el rbol sintctico.
Record
Var (x)
Var (y)
Pointer
Array (10)
Real
Int
Esto slo es posible en algn lenguaje de programacin, si el compilador reconoce que la variable destino tiene la suficiente precisin para contener el valor origen. En Java se puede almacenar un valor byte en una variable int, dado que este tipo de datos es de mayor precisin que el primero. A esto se le llama ensanchamiento o promocin, dado que el tipo ms pequeo se ensancha o promociona al tipo compatible ms grande. Si por el contrario, se desea asignar un valor de variable int a una variable byte se necesita realizar una
Conversin de tipos explcita. En algunos casos se puede realizar la conversin pero se pueden perder datos, como por ejemplo al pasar un valor flotante a un entero. A esto se le llama estrechamiento, dado que se estrecha explcitamente el valor para que quepa en el destino. La conversin de un tipo se realiza poniendo delante un nombre de tipo entre parntesis, por ejemplo, (tipo) valor. byte a; int b; a=(byte) b; Existen dos tipos de comprobacin: esttica y dinmica. La comprobacin ayuda a evitar la mayora de los errores de programacin. Ejemplos: Comprobacin de tipos. Para saber si el operador aplicado a los operadores es correcto Comprobacin de flujo de control. Se debe verifica que las instrucciones que cambia el flujo de un programa sean vlidos. Ejemplo: break, goto. Comprobacin de unicidad: definir un objeto una sola vez. Comprobacin relacionadas con nombres. El mismo nombre debe aparecer dos veces. Variables que se declaran pero no utilizan. La comprobacin de tipos es la ms complicada. Las dems comprobaciones son rutinarias. El operador % ocupa que los dos operandos sean enteros. es una funcin suma(a,b) que est sobrecargada para distintos tipos de datos. Siempre se disean reglas de tipos como los valores numricos se convierten al de mayor jerarqua o el tipo de datos punteros slo apunta al tipo de datos declarado. Algunos lenguajes revisan el tamao de los arreglos (Java) de manera esttica otros lo hacen de manera dinmica (en tiempo de ejecucin). Diferenciar el uso de +, * enteros que con puntero (aritmtica de punteros)
Al conjunto de reglas que se definen para la comprobacin de los tipos de datos se denomina sistema de tipos La mayora de veces la recuperacin de errores se l suele omitir ya que el programa no finaliza pero tal vez no obtenga los valores deseados.
Generalmente en la etapa de anlisis sintctico se suelen agregar los tipos a la tabla de smbolos. Se revisa el rbol sintctico para comprobar los tipos asignados. Existen conversiones explcitas en las cuales el usuario indica el tipo de datos a = (int)(23.3/18.2); Las conversiones implcitas requieren de mayor tiempo de ejecucin. Un ciclo de 1 a N tard 5.4 y 48.4 nanosegundos utilizando conversiones implcitas. Polimorfismo: una funcin puede tener el mismo nombre con diferentes elementos. El tipo de datos debe ser diferente. Un ejemplo de polimorfismo son las plantillas en algn lenguaje de programacin. Se debe considerar el mbito de las variables (locales y globales).
Conversin de tipos Una extensin comn de las reglas de tipo de un lenguaje es permitir expresiones aritmticas de tipo mezclado tal como 2 + 3.1 donde se suman un nmero real y un nmero entero. En tales casos, debe hallarse un tipo comn que sea compatible con todos los tipos de las subexpresiones y deben aplicarse operaciones para convertir los valores en tiempo de ejecucin a las representaciones apropiadas antes de aplicar el operador. En el ejemplo anterior el entero 2 debera convertirse a punto flotante antes de la suma, y la expresin resultante ser del tipo suma. Existen dos formas que un lenguaje pueda tomar para tales conversiones. Una forma que el programador suministrase la funcin de conversin de manera que ejemplo anterior sera FLOAT(2) + 3.1 o se causar un error de tipo. El otro mtodo consistira en que el verificador de tipo suministre la operacin de conversin de manera automtica, basndose en los tipos de las subexpresiones. Las conversiones automticas se conocen como coercin. La coercin se puede expresar de manera implcita mediante el verificador de tipo, ya que el tipo inferido de una expresin cambia a partir de una sobreexpresin.
MINUS
Los otros tokens no necesitaran tener un valor. Por otra parte el tipo asociado a un token debe por supuesto coincidir con el tipo de token que el scanner retorne. Para una regla ABCD, la accin semntica debe retornar un valor cuyo tipo es el asociado al no terminal A. Pero puede construir este valor de los valores asociados a los terminales y no terminales B, C, D.
Recursivo-descendente En un parser recursivo-descendente, las acciones semnticas son los valores retornados por las funciones de parsing, o los efectos laterales de esas funciones o ambos. Por cada smbolo terminal y no terminal, asociamos un tipo (desde el lenguaje de implementacin del LP del compilador) de valor semntico representando frases derivadas desde ese smbolo. El siguiente programa es un intrprete recursivo descendente para una parte de la gramtica en la cual eliminamos la recursin por la izquierda (por conveniencia la volvemos a mostrar):
S E$ l T F T F id
E T E T * F T F num
E + T E T / F T F (E) T l
E - T E
Pila semntica Los problemas de integracin entre los subsistemas son sumamente costosos y muchos de ellos no se solucionan hasta que la programacin alcanza la fecha lmite para la integracin total del sistema. Se necesita una memoria auxiliar que nos permita guardar los datos intermedios para poder hacer la comparacin.
La tabla de smbolos tambin recibe el nombre de ambiente. Un ambiente contiene un conjunto de parmetros que slo son visibles en ese ambiente.La tabla de smbolos se mantiene durante todo el proceso de traduccin agregando elementos especficos en cada paso. Operaciones sobre la tabla de smbolos Inserta(smbolo) Existe(nombre) Tipo(nombre) TIPO {tipo = obtengo (yytext());} Declaracin listavar PYC var {inserta(smbolo);} | var Listavar {Inserta (smbolo) ; } ID {simbolo=yytext; smbolo.tipo=tipo; Var simbolo.amb=ambito;} Operaciones sobre la tabla de smbolos PI exprlog {A=A;} PD Exprlog |NOT exprlog {A=A;} |exprlog {A1=A;} OPLOG exprlog {A2=A If(A1==INT && A2==INT) A=INT; Else A=ERROR_TIPO;} El anlisis semntico conecta las definiciones de las variables con sus usos, checa que cada expresin tenga un tipo correcto y traduce la sintaxis abstracta a una representacin mas simple para generar cdigo mquina.
Esta fase es caracterizada por el mantener la tabla de smbolos (tambin llamada environment) la cual mapea identificadores con sus tipos y localidades. Cada variable local en un programa tiene un mbito (scope) dentro del cual es visible. Por ejemplo, en un mtodo Mini Java m, todos los parmetros formales y variables locales declarados en m son visibles solo hasta que finalice m. Un ambiente es un conjunto de atados (bindings) denotados por . Por ejemplo, podemos decir que el ambiente z0 contiene los atados {gstring,aint}, que significa que el identificador a es una variable entero y g es una variable string. Ejemplo
Suponer que compilamos esta clase en el ambiente z0. Las declaraciones de campo en lnea 2 nos da la tabla z1 igual a z0 + {aint,bint,cint}. Los identificadores en lnea 4 pueden encontrarse (look up) en ambiente z1. En lnea 5, la tabla o ambiente z2=z1+{jint} es creada; y en lnea 6, z3=z2+{astring} es creada. Implementacin de la Tabla Existen dos opciones: El estilo funcional donde cuando z1 existe y z2 es creado, z1 sigue existiendo. Y el imperativo en donde z1 es destruido al crearse z2. Mientras z2 existe no podemos mirar z1. Pero al morir z2, z1 de nuevo existe. Mltiple Tablas de Smbolos En algunos LP pueden existir varios ambientes a la vez: Cada mdulo, o clase o registro en el programa tiene una tabla de smbolos z propia. Ejemplos (ML y Java).
Al analizar los 2 programas anteriores, sea z0 el ambiente base conteniendo funciones predefinidas, y sea z1={aint} z2={Ez1} z3={bint,aint} z4={Nz3} z5={dint} z6={Dz5} z7=z2+z4+z6 En ML, N es compilado usando el ambiente z0+z2 para buscar los identificadores en la tabla;D es compilado usando z0+z2+z4 y el resultado del anlisis es {Mz7}. En Java, referencias adelantadas son permitidas (dentro de N la expresin D.d sera legal), asi E,N y D son compilados en el ambiente z7. TABLAS DE SIMBOLOS EN LENGUAJES IMPERATIVOS Un programa grande puede contener miles de distintos identificadores. Esto hace que la bsqueda en la tabla (loock up) tenga que ser eficiente. En ambientes imperativos usualmente se usan tablas de dispersin. La operacin z=z+{at} es implementada insertando t en la tabla de dispersin usando la llave a. Una tabla de dispersin con encadenamiento externo funciona bien y soporta eliminacin fcil de at para recuperar z al final del mbito de a. El siguiente programa implementa una tabla de dispersin. El bucket i es una lista ligada de todos los elementos cuya llave genere i mod SIZE. Considere z+{at2} cuando z ya contiene at1. La funcin insert deja at1 en el bucket y pone at2 antes en la lista. Entonces, cuando pop se realiza despus del ambito de a, z es restaurado. SIMBOLOS
Para evitar comparaciones innecesarias de cadenas podemos convertir cada cadena a un smbolo, y as todas las diferentes ocurrencias de cualquier cadena se conviertan a un mismo objeto smbolo. El mdulo smbolo implementa los smbolos y tiene estas propiedades: Comparar smbolos por igualdad o por mayor es rpido (comparacin por apuntador o por entero). Extraer una llave hash de tipo entero es rpido Los ambientes son implementados en la clase Symbol.Table como Tables mapeando Symbols a ligados (bindings). Para eso se manejan ligaduras para diferentes propsitos en el compilador ligadura para tipos, para variables, para funciones, etc. Entonces, una ligadura es un Objeto. Para implementar la clase Symbol, usaremos el mtodo intern() (java.lang.String), para darnos un objeto nico a partir de una cadena de caracteres. Para el uso de la tabla de smbolos usaremos java.util.Hashtable. La funcin beginScope recuerda el estado actual de la tabla y endScope restaura la tabla a donde estaba en el mas reciente beginScope que no ha terminado. Cuando la atadura xb es metido a la tabla (table.put(x,b)), x es dispersado a un ndice i, y un objeto binder xb es puesto en la cabeza de la lista ligada para el bucket i. Si la tabla ya tiene una ligadura xb, esto permanecera en el bucket, pero escondido por xb. Esto es importante ya que soportara la implementacin de undo (beginScope y endScope). Tambin deber existir una pila auxiliar, que muestre en que orden los smbolos son metidos (pushed) a la tabla de smbolos. Cuando xb es encontrado, entonces x es metido a la pila (beginScope). Entonces, para implementar endScope, los smbolos deben sacarse de la pila. Por ejemplo, considere la siguiente figura, que muestra un programa y su tabla de smbolos.
En casi cualquier LP, una funcin (mtodo) puede tener variables locales que son creadas cuando se llama la funcin (al entrar a esta). Diferentes invocaciones a la funcin pueden existir a la vez, y cada invocacin tiene su propia instanciacin de variables. En el siguiente mtodo de Java
Una nueva instancia de x es creada (e inicializada por el llamador de f) cada vez que f es llamada. Debido a que existen llamadas recursivas, muchas de esas x existen simultneamente. Similarmente, una nueva instancia de y es creada cada vez que el cuerpo f es iniciado. En muchos LP (incluyendo Pascal, C y java), las variables locales son destruidas cuando una funcin retorna. Ya que las variables locales son creadas y destruidas en una forma LIFO, podemos usar una pila para manejarlas.
MARCOS DE PILA Debido a que se trabaja con bloques de datos por funcin un push y pop no funciona. Entonces la pila es tratada como si fuera un gran arreglo, con un registro especial- el stack pointer que apunta a una localidad. Todas las localidades despus del apuntador son basura y todas las que estn antes estn asignadas. El rea en la pila dedicada a las variables locales, parmetros, direccin de retorno y otras variables temporales para una funcin es llamada el registro de activacin o marco de pila de la funcin. El diseo de la estructura de los marcos es de acuerdo con la arquitectura y el LP que se compila. Aunque normalmente el constructor de la arquitectura define un diseo de marco standard para todos los compiladores para esa arquitectura.
Marco de Pila Los argumentos de entrada son los pasados por el llamador (tcnicamente son parte del marco anterior pero pueden accesarse usando un desplazamiento del apuntador de marco). Cuando la funcin actual llama otras funciones, puede usar el espacio de los argumentos de salida para pasar parmetros. La direccin de retorno es creada por la instruccin CALL. Las variables locales tambin tienen su espacio. Las variables mantenidas en registros algunas veces son salvadas a memoria. El Apuntador de Marco (FP) Suponer que una funcin g() llama la funcin f(a1,an). Diremos que g es el llamador (caller) y f el llamado (callee). Al entrar a f, el apuntador de la pila (SP) apunta al primer argumento que g pasa a f. Al entrar, f coloca un marco solo con restar el tamao del marco de el SP. El viejo SP se convierte en el actual FP y el viejo FP es salvado en el marco. Cuando FP termina, solo copia FP de regreso a SP y regresa el valor viejo salvado de FP. Si los marcos son siempre del mismo tamao entonces no es necesario contar con FP y todo se simplifica sumando o restando la constante framesize a SP. Registros Por eficiencia, es importante mantener las variables locales, resultados intermedios y otros valores en registros en lugar de la pila de marcos. Si funcin f llama a g y ambas hacen uso de registro r, entonces r debe ser salvado (dentro de la pila de marcos) antes de que lo use g y restaurado (desde la pila) despus de que termine g.
de quien es responsabilidad de salvar r? de f o g? si lo salva f se dice que r es un registro caller-save; si lo salva g se llama callee-save. Pase de Parmetros Estudios actuales han mostrado que raramente una funcin pasa mas de 4 parmetros. Debido a esto, la mayora de las mquinas definen que los primeros k argumentos (con k=4) se pasan en registros y el resto en memoria. Direcciones de Retorno Si g llama a f, entonces si la instruccin call dentro de g est en direccin a, el lugar de retorno en g es a+1, la siguiente instruccin del call. En mquinas modernas la direccin de retorno es pasada a un registro en lugar de la memoria. En funciones hoja la direccin no necesita ponerse en la pila. Registros vs. Memoria Registros siempre deben usarse en asignacin a menos que: La variable sea pasada por referencia La variable es accesada por una funcin anidada dentro de la funcin actual. El valor es demasiado grande para un registro. La variable es un arreglo, donde es necesario realizar aritmtica de direcciones. El registro que contiene la variable es necesitado para otros propsitos. Existen demasiadas variables locales y valores temporales
Ligas Estticas (Static Links) En LP que admiten funciones anidadas (Pascal,ML y Java) las funciones de mas adentro pueden usar variables declaradas en funciones de mas afuera (Estructuras de Bloque). En el siguiente programa (sig. Diapositiva) la funcin write hace referencia a la variable de afuera output e indent hace referencia a n y output. Para cumplir con esto, indent debe tener acceso no solo a su propio marco (para i y s) sino tambin a los marcos de show (por n) y prettyprint (por output). Programa de funciones Anidadas
Existen varios mtodos para solucionar lo anterior: Siempre que una funcin f sea llamada, puede pasarse un apuntador a el marco de la funcin que estticamente encierra a f; este apuntador es la liga esttica. Un arreglo global puede mantenerse, conteniendo -en posicin i - un apuntador a el marco del procedimiento mas recientemente activado cuyo profundidad de anidamiento esttico es i. Este arreglo es llamado un display.
CONCLUSION
Se compone de un conjunto de rutinas independientes, llamadas por los analizadores morfolgico y sintctico. El anlisis semntico utiliza como entrada el rbol sintctico detectado por el anlisis sintctico para comprobar restricciones de tipo y otras limitaciones semnticas y preparar la generacin de cdigo. En compiladores de un solo paso, las llamadas a las rutinas semnticas se realizan directamente desde el analizador sintctico y son dichas rutinas las que llaman al
generador de cdigo. El instrumento ms utilizado para conseguirlo es la gramtica de atributos. En compiladores de dos o ms pasos, el anlisis semntico se realiza independientemente de la generacin de cdigo, pasndose informacin a travs de un archivo intermedio, que normalmente contiene informacin sobre el rbol sintctico en forma linealizada (para facilitar su manejo y hacer posible su almacenamiento en memoria auxiliar). En cualquier caso, las rutinas semnticas suelen hacer uso de una pila (la pila semntica) que contiene la informacin semntica asociada a los operandos (y a veces a los operadores) en forma de registros semnticos.