• No se han encontrado resultados

Tabla de símbolos y Gramáticas con atributos

N/A
N/A
Protected

Academic year: 2021

Share "Tabla de símbolos y Gramáticas con atributos"

Copied!
44
0
0

Texto completo

(1)

Compiladores e Interpretes Compiladores e Interpretes

Análisis Semántico Verificación de Tipos

Luis Ochoa

[email protected]

(2)

Tabla de símbolos y Gramáticas con atributos

Se desea desarrollar una gramática atribuida que haga uso de una tabla de símbolos.

La gramática a utilizar es la siguiente:

Gramática:

S exp

exp (exp ) | exp + exp | id | num | let dec-list in exp dec-list dec-list , decl | decl

En la gramática el símbolo S, se coloca para poder inicializar los atributos heredados.

La única adición sobresaliente a la misma es la expresión let, la cual se compone de una secuencia de declaraciones separadas por comas de declaraciones de la forma id = exp. Por ejemplo:

▫ let x = 2+1, y=3+4 in x+y

dec-list dec-list , decl | decl decl id = exp

(3)

Semánticamente una expresión let posee las siguientes características:

▫ Las declaraciones después del token let establecen

nombres para las expresiones que, cuando aparecen en la exp después del token in simbolizan los valores de

Tabla de símbolos y Gramáticas con atributos

la exp después del token in simbolizan los valores de las expresiones que representan.

▫ Por ejemplo en let x = 2+1, y=3+4 in x+y

 x representa el valor de 3 (2+1).

 y representa el valor de 7 (3+4).

 De esta forma la expresión let tiene el valor de x+y=3+7=10

▫ Pudiendo inferir que las declaraciones dentro de la expresión let representan una clase de declaración

constante y que las expresiones de let representan este lenguaje.

(4)

Por esto la gramática permite anidaciones arbitrarias de expresiones let entre si mismas, por ejemplo:

▫ Por lo que se fijan las siguientes reglas de ámbito:

 No habrá declaración repetida del mismo nombre dentro de la misma expresión let, siendo ilegal expresiones como:

 let x=2, x=3 in x+1

let x=2 , y=3 in

(let x=x+1, y=(let z=3 in x+y+z) in (x+y)

)

Tabla de símbolos y Gramáticas con atributos

 Si no es declarado cualquier nombre en alguna expresión let circundante, también se obtiene un error como en:

 let x=2 in x+y

 El ámbito de cada declaración en una expresión let se extiendo sobre el cuerpo de let de acuerdo con la regla de anidación más próxima para estructuras de bloque, así el valor de la siguiente expresión es 3 y no 2 (el x en la declaración interior se refiere a x=3 y no a 2):

 let x=2 in (let x=3 in x)

 Finalmente, definimos la interacción de las declaraciones en una lista de declaraciones en el mismo let para que sea secuencial, de este modo en la expresion siguiente la primera y tiene valor de 3, la segunda x tiene valor 5, y la segunda y tiene valor 8, teniendo asi la expresion completa valor de 8:

 Let x=2, y=x+1 in (let x=x+y , y=x+y in y)

(5)

Ahora se desarrollan ecuaciones de atributo que utilicen la tabla de símbolos para estar al tanto de las declaraciones en expresiones let y expresar las reglas de ámbito e interacción descritas. (por

simplicidad solo se verificara esto y no se hará calculo de valores)

Tabla de símbolos y Gramáticas con atributos

calculo de valores)

Los siguientes atributos son importantes:

▫ err: si la expresión es correcta esta en true sino en false.

▫ symtab: tabla de símbolos representada como.

▫ nestlevel: representa el nivel de anidación.

(6)

Tabla de símbolos

y Gramáticas con atributos

Gramática con atributos para expresiones con bloques let:

(7)

 Las funciones de la tabla de símbolos son:

 Insert: inserción de símbolos.

 isin: devuelve true si el símbolo usado como parámetro esta en la tabla de símbolos.

 lookup: devuelve un entero dando el nivel mas reciente del símbolo usado como parámetro.

Tabla de símbolos y Gramáticas con atributos

 En el nivel más alto de la gramática se asignan los valores de los dos atributos heredados y se eleva el valor del atributo sintetizado. De este modo la regla gramatical S exp tiene las tres ecuaciones de atributo asociadas:

 exp.symtab=emptytable

 exp.nestlevel=0

 S.err=exp.err

 Se debe advertir que el nivel de anidación también se incrementa en uno tan pronto se introduce en el bloque let.

(8)

Comprobación de tipos

• Un tipo de datos es construido de forma simple o tipos base (int, double, etc.) y los constructores de tipo pueden crear “nuevos tipos” basados en de tipo pueden crear “nuevos tipos” basados en los tipos existentes: struct, union, [] (arrary), etc.

• Los tipos son comprobados en el código al examinar la “compatibilidad” de los tipos

componentes y al determinar el tipo “resultado”, si existe alguno.

(9)

Comprobación de tipos

Ejemplo en C:

Supongamos que se declara una función como char * f(double d)

El tipo de datos de f es entonces char*()(double)

El tipo de datos de f es entonces char*()(double) (función desde double hacia char*)

La llamada f(2) chequea el tipo porque si f es una función, y 2 es un int, y int es compatible en C con double (puede ser convertido de forma silenciosa e implicita). El resultado entonces debe ser de tipo

char*

(10)

Comprobación de tipos

• En términos de un árbol de sintaxis:

call

(3) result has type char*

(1) is function

num: 2 id: f

char*()(double) int (2) compatible with

(3) result has type (1) is function

(11)

Comprobación de tipos

Sistema de tipos: reglas de un lenguaje que

permiten asignar tipos a las distintas partes de un programa y verificar su corrección.

Formado por las definiciones y reglas que permiten

Formado por las definiciones y reglas que permiten comprobar el dominio de un identificador, y en qué contextos puede ser usado.

Cada lenguaje tiene un sistema de tipos propio,

aunque puede variar de una a otra implementación.

La comprobación de tipos es parte del análisis semántico.

(12)

Comprobación de tipos

• Funciones principales:

▫ Inferencia de tipos: calcular y mantener la información sobre los tipos de datos.

▫ Verificación de tipo: asegurar que las partes de un programa tienen sentido según las reglas de tipo del lenguaje.

de tipo del lenguaje.

• La información de tipos puede ser estática o dinámica:

▫ LISP, CAML o Smalltalk utilizan información de tipos dinámica.

▫ En ADA, Pascal o C la información de tipos es estática.

▫ También puede ser una combinación de ambas formas.

(13)

Comprobación de tipos

• Cuantas más comprobaciones puedan realizarse en la fase de compilación, menos tendrán que realizarse durante la ejecución

▫ Mayor eficiencia del programa objeto.

• Es parte de la comprobación de tipos:

▫ Conversión de tipos explícita:

transformación del tipo de una expresión con un propósito determinado.

▫ Coerción: conversión de tipos que realiza de forma implícita el compilador.

(14)

Comprobación de tipos

Conversión de tipos explícita: el programador indica el tipo destino.

▫ Funciona como una llamada a función: recibe un

▫ Funciona como una llamada a función: recibe un tipo y devuelve otro.

Conversión de tipos implícita: el compilador convierte automáticamente

elementos de un tipo en elementos de otro.

▫ La conversión se lleva a cabo en la acción semántica de la regla donde se realiza.

(15)

Comprobación de tipos

Comprobador de tipos seguro: Durante la compilación (comprobación estática) detecta todos los posibles errores de tipo.

Lenguaje fuertemente tipado: Si un

fragmento de código compila es que no se van a producir errores de tipo.

▫ En la práctica, ningún lenguaje es tan fuertemente tipado que permita una completa comprobación estática.

(16)

Comprobación de tipos

Información de tipos dinámica: El compilador debe

generar código que realice la inferencia y verificación de tipos durante la ejecución del programa que se está compilando.

#let primero (a,b) = a;;

primero : 'a * 'b -> 'a = <fun>

Información de tipos estática:

▫ Se utiliza para verificar la exactitud del programa antes de la ejecución.

▫ Permite determinar la asignación de memoria necesaria para cada variable.

function Primero(a,b:integer):integer;

begin

Primero:= a end;

(17)

Comprobación de tipos

Tipo de datos = conjunto de valores + operaciones aplicables

En el ámbito de los compiladores, un tipo se define mediante una expresión de tipo (información de tipos explícita)

explícita)

▫ Nombre de tipo: float

▫ Expresión estructurada explícita: set of integer

▫ Estas expresiones se utilizan en la construcción de otros tipos o para declarar variables.

• También es posible incluir información de tipos implícita:

const MAX = 10;

(18)

Comprobación de tipos

• La información de tipos, implícita o explícita, se mantiene en la tabla de símbolos.

▫ Esta información se recupera de la tabla de símbolos mediante el verificador de tipo cuando se hace

referencia al nombre asociado.

• Ejemplo:

• Ejemplo:

A[i] si A es de tipo array [1..10] of real

si i tiene tipo integer

entonces A[i] es correcto y su tipo es real Saber si i ∈∈ 1..10, no es una verificación de tipo,

sino de si el rango es o no correcto

(19)

Expresiones de Tipo

• Un lenguaje de programación contiene un

conjunto de tipos predefinido denominados tipos simples.

▫ Algunos lenguajes permiten definir nuevos tipos simples: enumerado, subrango.

• Todos los lenguajes permiten crear nuevos tipos complejos a partir de otros más simples mediante constructores de tipos:

▫ Matrices, productos, registros, punteros, funciones,

▫ En Pascal: array, set, record, ...

▫ En C++: struct, class, union, ...

(20)

Expresiones de Tipo

• Para analizar los diferentes tipos que intervienen dentro de un programa, el compilador debe contar con una estructura interna que le permita manejar cómodamente las expresiones de tipos.

• Esta estructura interna:

• Esta estructura interna:

▫ Debe ser fácilmente manipulable, pues su creación se realizará conforme se hace la lectura del

programa fuente.

▫ Debe permitir comparar fácilmente las expresiones asignadas a distintos trozos de código,

especialmente a los identificadores de variables.

(21)

Expresiones de Tipo

• Una forma de representación son los grafos acíclicos dirigidos (GADs).

▫ La ventaja de estas representaciones es que ocupan poca memoria y por tanto la

comprobación de equivalencia se efectúa con comprobación de equivalencia se efectúa con rapidez.

(22)

Expresiones de Tipo: ejemplos

(23)

Expresiones de Tipo: ejemplos

(24)

Expresiones de Tipo

• El tipo de compatibilidad de los tipos

“construidos” (los no simples) generalmente

depende sobre la noción de cuando dos tipos son

“iguales” o equivalentes, o al menos relativamente parecidos.

parecidos.

• Ejemplo en C

struct {} x,z;

struct {} y;

y = x; // ilegal! (diferente tipo) z = x; // ok! Mismo tipo

(25)

Expresiones de Tipo

Igualmente:

struct A {} x;

struct A y;

y = x; // ahora esta bien!

•• struct A {} x;

declara un Tipo (con el nombre

“struct A”) y la variable x.

Reusando el nombre “struct A” devuelve el mismo tipo.

Escribiendo struct { } define un tipo con un nombre interno oculto, y por lo tanto no puede ser

referenciado (y reutilizado).

(26)

Equivalencia de tipos

Una característica importante del sistema de tipos de un lenguaje de programación es el conjunto de reglas que permiten decidir cuándo dos expresiones de tipo representan al mismo tipo.

En muchos casos estas reglas no se definen como parte de las especificaciones del lenguaje.

1. Diferentes interpretaciones de los creadores de compiladores.

2. Estas diferencias afectan a la compatibilidad entre diferentes dialectos de un mismo lenguaje.

Tres formas de equivalencia: estructural, nominal y funcional.

(27)

Equivalencia de tipos

Equivalencia estructural: cuando se trata del

mismo tipo básico, o se forman por aplicación de un mismo constructor a tipos estructuralmente

equivalentes, Ejemplo:

Los tipos de las variables array1, matriz, vecvec1 y vecvec2 son estructuralmente equivalentes, pues su expresión de tipos es:

▫ array(0-5,array(0-5,real))

La variable vector no lo es. Expresión de tipos:

▫ array(0-5,real)

(28)

Equivalencia de tipos

Equivalencia nominal: dos tipos son nominalmente equivalentes cuando son

estructuralmente equivalentes considerando como tipos básicos e indivisibles a los identificadores de tipos. Ejemplo:

Las variables vector1 y vector2 son nominalmente equivalentes, porque en ambos casos su expresión de tipos es:

▫ array(0-5,tipovector)

La expresión de tipos de la variable array1 no puede considerarse nominalmente equivalente a las anteriores:

▫ array(0-5,array(0-5,real))

(29)

Equivalencia de tipos

Equivalencia funcional: Dos tipos se consideran

equivalentes funcionalmente cuando pueden emplearse indistintamente en un mismo contexto.

▫ Ejemplo: los tipos de A y de B son funcionalmente equivalentes:

Diferencia entre tipos compatibles y tipos funcionalmente equivalentes:

▫ En el primer caso, el compilador realiza sobre uno de ellos una transformación interna para que ambos se transformen en tipos equivalentes.

▫ En el segundo caso no se realiza ninguna modificación interna, sino que se relaja el criterio de equivalencia.

(30)

Algoritmos de Equivalencia

Resumiendo

▫ Equivalencia Estructural: Siempre y cuando los tipos tengan la misma estructura ellos son equivalentes.

▫ Equivalencia Nominal: los tipos son equivalentes

▫ Equivalencia Nominal: los tipos son equivalentes solamente si poseen nombres idénticos.

▫ Equivalencia funcional: los tipos son equivalentes si conducen de vuelta (mediante el cambio de nombre) al mismo uso original de un constructor de tipo

(31)

Algoritmos de Equivalencia

• Ejemplo de equivalencia struct A {};

typedef struct A A;

typedef struct {} B;

typedef struct {} B;

struct A x; A y; B z;

x, y, z son estructuralmente equivalentes

x, y equivalentes en la declaración, pero z no es equivalente en la declaración a los mismos,

ninguno de ellos es equivalente nominalmente.

(32)

Ejemplo Equivalencia de tipos

• En la gramática

• Podríamos verificar esta declaración:

(33)

Ejemplo Equivalencia de tipos

Con el pseudocódigo tenemos la función typeEqual que prueba la equivalencia de expresiones de tipo de la gramática:

(34)

Representación interna de los tipos en un compilador

Ya que los tipos son construidos de forma recursiva, una estructura de árbol debe ser usada (el árbol de sintaxis toma otro nodo importante: tipo de dato).

Algunos lenguajes (FORTRAN, TINY, etc.) tienen espacios de almacenamiento iguales para los tipos de datos, por lo de almacenamiento iguales para los tipos de datos, por lo que una enum puede ser usada: int, intarray, function.

Las funciones son generalmente constructores de tipo

también, pero sus tipos no tienen que ser explícitos, ya que el tipo de retorno y los tipos de los parámetros están

disponibles para el árbol de sintaxis para comprobación (a menos por supuesto, que los tipos puedan ser

explicitamente escritos, como en c:

typedef char*(F)(double))

(ver ejemplos siguiente lamina)

(35)

Digresión sobre las funciones de tipos en C

Hay dos tipos de funciones en C que son casi identicas (y que pueden ser casi usadas de forma intercambiable), funciones constantes y funciones de punteros:

typedef char* F(double);

typedef char* (*G)(double);

typedef char* (*G)(double);

F es un tipo de función “constante” (un prototipo),

mientras que G es un “puntero a un tipo de función”, o una variable de función

F f; // a prototype for a func f G g = f; // g is var init’ed to f f = g; // illegal - f is const

Digresión: Desviación en el hilo de un discurso oral o escrito para expresar algo que se aparta del tema que se está tratando

(36)

De muchas formas, esto replica la relación cercana existente en C entre punteros y arrays:

int x[10];

int* y = x; // ok x = y; // illegal

Digresión sobre las funciones de tipos en C

x = y; // illegal

En las llamadas y parámetros esto realmente no importa cual de estos tipos se use o asuma: f(2), (*f)(2) y

(&f)(2) ya que todo trabajara perfectamente, y void p(F ff) o void p(G gg) son idénticas en efecto.

(37)

Tipos recursivos

• Presentan un problema especial:

struct A { int x; struct A next; };

▫ Es ilegal, porque podría representar un tipo infinito (tal cual y como void f(void){ f();} representa una llamada infinita).

• En C debe ser interpuesto un puntero:

struct A { int x; struct A* next; };

• Algunos lenguajes usan uniones en lugar de punteros.

• Otros como Java tienen punteros implícitos.

(38)

Otros problemas

• ¿Debe ser el tamaño del array parte de su tipo?

▫ C dice no.

• ¿Qué tan lejos debe llegar la

• ¿Qué tan lejos debe llegar la compatibilidad de tipos?

▫ ¿Deberían ser cuales quiera dos puntero compatibles?

• Tipos dinámicos: Construir los tipos

durante la ejecución.

(39)

Ejemplo de gramática atribuida para la verificación de tipos

• Observemos la siguiente gramática:

(40)

Ejemplo de gramática atribuida para la verificación de tipos

Donde se supone que los tipos se mantienen con una tipos se mantienen con una clase de estructura de árbol, por eso se usa la función makeTypeNode

(41)

Aspectos avanzados: sobrecarga

Sobrecarga: el mismo nombre de operador se refiere a diferentes funciones.

Resolución de la sobrecarga: determinación del tipo de cada expresión interviniente para

identificar la función a aplicar.

• La siguiente expresión es ilegal en C pero legal en C++:

int Primero(a,b:int) { return a; }

float Primero(a,b:float) { return a; }

(42)

Aspectos avanzados: sobrecarga

• El compilador debe eliminar la ambigüedad:

▫ Aumentando las búsquedas en la tabla de

▫ Aumentando las búsquedas en la tabla de

símbolos con información de tipos, pues no es suficiente con el identificador.

▫ Manteniendo conjuntos de tipos para cada

identificador y devolviendo el tipo adecuado en cada utilización del identificador

(43)

Aspectos avanzados: polimorfismo

Polimorfismo: una interfaz, métodos múltiples.

Un lenguaje es polimórfico si una entidad puede tomar más de un tipo.

La siguiente expresión es polimórfica:

La siguiente expresión es polimórfica:

procedure swap (var x,y: anytype)

El compilador debe determinar en tiempo de ejecución a qué tipo corresponden los patrones de tipo de una

expresión.

Existen algoritmos de verificación para tipos polimórficos, fundamentalmente en los lenguajes de programación

funcional, que implican técnicas sofisticadas de coincidencia de patrones.

(44)

Recordatorio entrega día Jueves

¿Qué deben entregar?

▫ La gramática en CUP sin acciones sintácticas (solo pasan lo que di en

palabras y gramática libre de contexto a cup), la misma debe hacer uso de la parte léxica de JFlex (esta parte debe variarse ya que debe es retornar los tokens declaradas en CUP al parser, que es el mismo CUP y no lo que se tokens declaradas en CUP al parser, que es el mismo CUP y no lo que se entrego en el primer proyecto)

Dificultad:

▫ Muy sencillo tiempo estimado de realización una hora o menos, lo único que debe aprender es como unir JFlex con CUP y pasar los tokens de uno a otro (diez minutos), notación BNF (ya la saben creo) y transformar el texto explicativo del proyecto a

notación BNF (media hora máximo).

¿Cómo pruebo si esta correcto lo que lleve a cabo?:

▫ Simplemente debe pasarle los archivo con los ejemplos de programas incluidos en el enunciado del proyecto, y si los verifica sintácticamente correcto (no sale ningún error en pantalla), y si además introduce errores sintácticos a propósito su analizador y este los detecta (la ejecución del parser se detiene con un mensaje de error) todo esta bien.

Referencias

Documento similar

En este ensayo de 24 semanas, las exacerbaciones del asma (definidas por el aumento temporal de la dosis administrada de corticosteroide oral durante un mínimo de 3 días) se

En un estudio clínico en niños y adolescentes de 10-24 años de edad con diabetes mellitus tipo 2, 39 pacientes fueron aleatorizados a dapagliflozina 10 mg y 33 a placebo,

• Descripción de los riesgos importantes de enfermedad pulmonar intersticial/neumonitis asociados al uso de trastuzumab deruxtecán. • Descripción de los principales signos

Debido al riesgo de producir malformaciones congénitas graves, en la Unión Europea se han establecido una serie de requisitos para su prescripción y dispensación con un Plan

Como medida de precaución, puesto que talidomida se encuentra en el semen, todos los pacientes varones deben usar preservativos durante el tratamiento, durante la interrupción

Abstract: This paper reviews the dialogue and controversies between the paratexts of a corpus of collections of short novels –and romances– publi- shed from 1624 to 1637:

Después de una descripción muy rápida de la optimización así como los problemas en los sistemas de fabricación, se presenta la integración de dos herramientas existentes

La Intervención General de la Administración del Estado, a través de la Oficina Nacional de Auditoría, en uso de las competencias que le atribuye el artículo