Examen final de CL Fecha de publicación de notas: 26-1-2010 Sin apuntes. Tiemp: 3h. Enero de 2010 Fecha de revisión: 27-1-2010 Nombre y Apellidos: [Problema 1. Análisis semántico (2.5 puntos)] Supongamos que tenemos una versión del lenguaje CL a la que se la ha añadido el tipo básico Real, y los punteros a cualquier tipo. El operador postfijo ^ sirve para acceder al objeto apuntado, y el operador prefijo & permite obtener la dirección de una expresión. Para simplificar, asumiremos coerción de entero a real (pero no en sentido inverso) cuando sea necesario. A continuación tenemos un programa en CL. 1: Program 2: Vars 3: x Real 4: v Array [1..20] of Pointer to Array [2..9] of Real 5: u Array[4..7] of Struct x Pointer to Real y Real Endstruct 6: Endvars 7: Procedure P(Val y Int) 8: v[u[5].x^]^[6] := x + y 9: Endprocedure 10: Procedure Q(Val x Struct x Pointer to Real y Real Endstruct) 11: Vars 12: v Array [0..30] Of Pointer to Struct a Int b Real Endstruct 13: Endvars 14: P(31) 15: Endprocedure 16: Q(u[5]) 17: Endprogram Apartado a) [0.5 puntos] Mostrad el contenido de la tabla de sı́mbolos inmediatamente antes de analizar semánticamente la porción del AST (Árbol de Sintaxis Abstracta) correspondiente a la lı́nea 8. Apartado b) [0.75 puntos] Dibujad el AST de la instrucción de la lı́nea 8. Decorad (anotad) los nodos del árbol con información del tipo de cada subexpresión y de si es o no referenciable. Usad el tipo error si no podéis inferir un tipo, pero no propaguéis innecesariamente los errores. Apartado c) [0.25 puntos] Numerad el AST en preordren y emitid los errores semánticos que encontréis indicando el número del nodo del AST donde se detectan. Sin apuntes. Tiemp: 3h. Nombre y Apellidos: Apartado d) [0.75 puntos] Dibujad de nuevo el AST de la instrucción de la lı́nea 8. Suponiendo visibilidad dinámica y que se ha llegado a la lı́nea 8 después de haber ejecutado el programa principal, decorad (anotad) los nodos del árbol con información del tipo de cada subexpresión y de si es o no referenciable. Recordad que el lenguaje CL estandard se basa en visibilidad estática y el árbol se decora en tiempo de compilación. Ahora se trata de decorar el AST con la información de visibilidad obtenida en tiempo de ejecución. Tal como se os pedı́a en el apartado b), usad el tipo error si no podéis inferir un tipo, pero no propaguéis innecesariamente los errores. Indicad claramente que ha cambiado respecto b). Apartado e) [0.25 puntos] Manteniendo la numeración de los nodos del AST de las preguntas anteriores y suponiendo visibilidad dinámica, emitid los errores semánticos que encontréis indicando el número del nodo del AST donde se detectan. Sin apuntes. Tiemp: 3h. Nombre y Apellidos: [Problema 2. Generación de código (2.5 puntos)] Queremos extender el lenguaje CL para considerar el tipo PQueue que representa colas de prioridad de tamaño máximo fijo. Por ejemplo, el siguiente programa en CL: program vars Q PQueue[5] of Char T Array[8] of Char a Char endvars T[3] := ’e’ push(Q,8*2,’i’) push(Q,100,’m’) push(Q,24,’s’) push(Q,97-2,T[2+1]) push(Q,25,’s’) while (Not empty(Q)) Do a := pop(Q) Write(a) EndWhile endprogram declarará una cola de prioridad de tamaño 5 e imprimirá los carácteres ’m’,’e’,’s’,’s’,’i’ (asumid que el tipo Char existe en CL). Como se puede ver, la cola está ordenada por prioridad descendente. La semántica de cada operación es: • push(Q,expr1 ,expr2 ) inserta expr2 en Q con la prioridad especificada por expr1 . Si Q está llena esta operación no hace nada. • pop(Q) devuelve la cima (elemento con prioridad más alta) y lo desapila de Q. Si Q está vacı́a no hace nada. • empty(Q) devuelve un booleano que determina si Q está vacı́a. PISTA: no es necesario que la implementación sea eficiente en tiempo de ejecución, por tanto evitad soluciones complejas como por ejemplo colas circulares. Se sugiere que mantengáis los elementos ordenados en la estructura de datos de la cola de forma que las operaciones de pop y push tengan como máximo coste lineal. Se pide1 : 1. (0.5 puntos) Proponed una forma de representar en t-código las colas de prioridad, la cual deberá tener la información necesaria para realizar las tres operaciones utilizadas en el código anterior. Es importante considerar que el tipo de los elementos puede ser no básico. Describid como serı́a la generación de código para la declaración de una variable de tipo PQueue. 2. (1.25 puntos) Describid formalmente la generación para las operaciones empty y pop. En este apartado se requiere el uso del formalismo explicado en clase (GenCode, GenLeft, GenRight) para describir cada operación. Justificad si es necesario el uso de espacio auxiliar (aux space). 3. (0.75 puntos) Describid informalmente (en un párrafo) como serı́a la generación de código para la operación push. Enumerad las llamadas a GenLeft y GenRight que habrı́a. 1 Recordad que GenRight es lo mismo que GenValue y que GenLeft es lo mismo que GenAddress. Sin apuntes. Tiemp: 3h. Nombre y Apellidos: [Problema 3. Optimitzación (2.5 puntos)] El código inicial distribuido en bloques básicos se muestra a continuación: 1: s = 0 2: 3: 4: 5: 6: f=a+c s=s+2 p=5*s x=c+d if (s < 15) goto 12 7: if (g > p) goto 15 8: 9: 10: 11: i=a+b r=2*s j=a*c goto 2 12: g = c + d 13: e = a * c 14: goto 17 15: g = a + b 16: e = g + c 17: print g+e Sin apuntes. Tiemp: 3h. Nombre y Apellidos: PREGUNTAS CORTAS 1. (0.5 puntos) La gramática del lenguaje de programación C incluye los tokens <, <<, > y >>. < y > son los operadores de comparación ”menor que”y ”mayor que”, respectivamente. << y >> son, respectivamente, los operadores de ”desplazamiento de bits a la izquierda”y ”desplazamiento de bits a la derecha. En el lenguaje C++, que es una extensión de C, < y > se utilizan también para introducir los parámetros de plantillas de clases (templates). Por ejemplo, el programa en C++ siguiente declara una variable de tipo vector a partir de una plantilla predefinida denominada vector. 1: 2: 3: 4: 5: 6: 7: #include <vector> using namespace std; int main() { vector<int> v; // Declara v como un vector de enteros. } Partiendo del modelo de análisis léxico visto en clase, razonad porque el siguiente programa en C++ 1: 2: 3: 4: 5: 6: 7: 8: #include <vector> using namespace std; int main() { vector<vector<int> > m; // Declara una matriz de nombre m // como un vector de vectores de enteros } no da ningún error mientras que el siguiente programa en C++ 1: 2: 3: 4: 5: 6: 7: #include <vector> using namespace std; int main() { vector<vector<int>> matriz; } da un error en la lı́nea 6. 2. (1 punto) Supongamos que tenemos una gramática donde IDENT y DOT son dos terminales (tokens) y expr es un no terminal. Supongamos también que la gramática tiene una sola regla que, escrita en PCCTS, es: expr: IDENT (DOT expr)* Razonad porqué la única regla de la gramática no es LL(1) mediante los conceptos de first (primero), de follow (siguiente) y nullable (anulable) vistos en clase. 3. (1 punto) Proponed y justificad una versión mejorada del patrón de generación de código siguiente2 : GenRight(expr1 and expr2 , ti ) ≡ GenRight(expr1 , ti ) || GenRight(expr2 , ti+1 ) || land ti ti+1 ti 2 Recordad que GenRight es lo mismo que GenValue y que GenLeft es lo mismo que GenAddress.