Modificación de la Práctica de CL 20 de Mayo de 2009 A continuación se presenta la modificación de la práctica que debéis realizar, junto con una guı́a de ayuda, que no es obligatorio seguir, y que quizás requiera de adaptaciones en vuestra práctica dependiendo de cómo la hayáis programado. Esta guı́a contiene también la información de cómo se realizará la evaluación, en base a unos juegos de prueba que podéis encontrar en el racó, en examens.fib.upc.edu accediendo a la entrega correspondiente, incluidos en el fichero infoexamen.tar, cuyo contenido podéis extraer con tar xf infoexamen.tar, para ejecutar a continuación tar xf jp.tar. El fichero infoexamen.tar contiene además otros ficheros que os permitirán realizar una autoevaluación. Al final se indica cómo realizar dicha autoevaluación y la entrega vı́a el racó. Es recomendable ir realizando entregas a medida que vuestra práctica vaya superando más juegos de pruebas. 3 puntos de la nota vienen dados por pasar los juegos de pruebas básicos de la práctica. El resto depende de lo que hagáis a continuación. Queremos realizar dos modificaciones a la práctica; ambas modificaciones son independientes (cada una tiene su parte sintáctica, semántica y de generación de código), pero es recomendable que empecéis por la primera, y, además, para superar el último juego de pruebas de la segunda parte necesitaréis haber completado la primera. Naturalmente, si os quedáis atascados en la primera, podéis intentar saltar a la segunda. La primera de estas modificaciones consiste en añadir un nueva construcción sintáctica que permita construir structs de forma rápida, a partir de una serie de expresiones cualesquiera. Por ejemplo, la expresión << 2, true >> construirı́a un struct con 2 miembros: el primero, de tipo int, con el valor 2; el segundo, de tipo bool, y con el valor True. Evidentemente, el resultado de esta expresión se podrá asignar en un variable; por ejemplo, en a :=<< 2, true >>, si a ha sido declarada de un tipo equivalente estructuralmente (es decir, aunque dos structs tengan miembros con nombres diferentes, siempre que los tipos sean individualmente compatibles, los structs serán de tipos equivalentes). La segunda modificación nos permitirá realizar el proceso inverso: una nueva sintaxis para el operador de asignación (sin modificar el actual) que, dada una serie de variables (más bien, expresiones referenciables) en el lado izquierdo, desempaquetará cualquier struct compatible pasado en el lado derecho. Por ejemplo, dado un struct s con tipo estructuralmente compatible con el del parágrafo anterior (<< 2, true >>), la instrucción << x, y >>:= s asignarı́a en x el valor del primer miembro del struct (un entero), y en y el valor del segundo miembro (un booleano). O bien darı́a los errores correspondientes si x o y son de tipos incompatibles o no referenciables, o si el struct s no tiene 2 miembros. Con las dos modificaciones realizadas, el siguiente programa: 1: Program 2: Vars 3: s Struct 4: a Int 5: b Int 6: c Int 7: EndStruct 8: x Int 9: y Int 10: z Int 11: EndVars 12: 1 13: s := <<2, 4, 6>> 14: <<x, y, z>> := s 15: 16: WriteLn(x) WriteLn(y) WriteLn(z) 17: EndProgram Escribirı́a 2, 4 y 6 por la pantalla. Antes de empezar, tenéis que modificar, en el archivo ptype.cc, la función equivalent types, ya que a partir de ahora queremos que no se tengan en cuenta los nombres de los miembros de los structs al realizar la comprobación de equivalencia de tipos. Si no habéis modificado nunca el archivo ptype.cc, podéis directamente sobreescribirlo con el archivo newptype.cc que adjuntamos. En concreto, se cambia la lı́nea 84, que contiene: if ((*it1)!=(*it2) || !equivalent_types(t1->struct_field[*it1],t2->struct_field[*it2])) { por: if (!equivalent_types(t1->struct_field[*it1],t2->struct_field[*it2])) { (0,25 puntos) El primer juego de pruebas sólo comprueba que hagáis esta simple modificación correctamente, ya que es necesaria para el resto de las modificaciones. 1: Program 2: Vars 3: a Struct 4: a Int 5: b Int 6: EndStruct 7: b Struct 8: x Int 9: y Int 10: EndStruct 11: c Struct 12: a Int 13: b Bool 14: EndStruct 15: EndVars 16: 17: a := b 18: a := c 19: EndProgram Type Checking: L. 18: Assignment with incompatible types. Parte I La primera parte consiste en la nueva expresión << e1, e2, . . . >> que construye un struct a partir de una serie de expresiones e1, e2, . . . Empezad añadiendo los nuevos tokens << y >>; recordad definirlos de la siguiente forma o tendréis problemas (ya que << tiene un significado especial para PCCTS incluso en una string): 2 #token OPENANGL "\<\<" #token CLOSEANGL "\>\>" En la gramática, deberéis añadir la nueva expresión, que tiene la misma prioridad que una llamada a una función (es una expresión básica). El contenido de << y >> sigue exactamente la misma sintaxis que la de una lista de parámetros reales, es decir, expresiones separadas con comas. Os sugerimos que diseñéis la gramática de forma que el nodo raı́z del arbol sintáctico generado por una expresión << e1, e2, . . . >> sea el token OPENANGL, y e1, e2, . . . sus hijos. Para el análisis semántico, el TypeCheck de esta expresión deberá averiguar el tipo de cada una de las expresiones e1, e2, . . . , y, a partir de esos tipos, construir un nuevo ptype (cuyo kind será, por razones obvias, struct). Este nuevo ptype construido será asociado al nodo del AST correspondiente. Una pequeña guı́a de qué es lo que el análisis semántico de esta expresión deberı́a hacer (que podéis usar o no): a->tp = create type("struct", 0, 0); para crear el ptype con kind struct (vacı́o), si a es el nodo OPENANGL del árbol sintáctico. Para cada expresión encontrada dentro de <<>>: • Averiguar de qué tipo es (con TypeCheck) • Añadir ese tipo al ptype a->tp, que lo podéis hacer con el siguiente fragmento de código; suponiendo que: a1 contiene el nodo del AST del cual acabáis de hacer TypeCheck y i es un contador que indica el número de expresión actual dentro de la construcción <<>> – empezando a contar desde 1 y que tendréis que incrementar en cada iteración del bucle. El objetivo de esto último es dar un nombre distinto a cada miembro del struct generado: e1 al primer miembro, e2 al segundo, etc. string s = "e" + itostring(i); a->tp->struct_field[s]=a1->tp; a->tp->ids.push_back(s); Importante: Por último, calcular los offset de los miembros del struct recién construido, ya que al no ser una variable local, no se hará automáticamente, y los necesitaréis para superar los juegos de pruebas posteriores. Lo haremos en el análisis semántico por simplicidad. Sólo tenéis que insertar, tras haber añadido todos los tipos de los miembros del struct, una llamada a compute_size(a->tp); Para poder usar está función tendréis que añadir el contenido de newsemantic.hh a vuestro semantic.cc, justo después de #include "ast.h" alrededor de la lı́nea 20. Esta nueva construcción no genera ningún error semántico nuevo, puesto que el struct construido no tiene ninguna limitación (puede tener cualquier número de elementos, cada elemento puede ser del tipo que sea, etc.). Sólo tendréis que dar los errores que se produzcan al evaluar el tipo de cada una de las expresiones, y verificaremos que generáis structs de tipo correcto intentando asignarlos a una variable. (1 punto) Con esto podréis pasar el segundo juego de pruebas: 3 1: Program 2: Vars 3: a Struct 4: v Int 5: EndStruct 6: b Struct 7: v Int 8: c Int 9: EndStruct 10: c Struct 11: d Struct 12: x Int 13: EndStruct 14: v Bool 15: EndStruct 16: x Int 17: v Bool 18: EndVars 19: 20: a := << >> 21: a := << 3 >> 22: a := << x + 2, 4 >> 23: b := << x, v + 2 >> 24: b := << x, v >> 25: c := << << 3 >>, True >> 26: c := << a , False >> 27: c := << << >>, True >> 28: a := << z >> 29: a := z 30: EndProgram Type Checking: L. L. L. L. L. L. L. L. 20: 22: 23: 24: 27: 28: 28: 29: Assignment Assignment Operator + Assignment Assignment Identifier Assignment Identifier with with with with with z is with z is incompatible incompatible incompatible incompatible incompatible undeclared. incompatible undeclared. types. types. types. types. types. types. Completado el análisis semántico de esta parte, vamos a pasar a la de generación del código que construya el struct. Para simplificar, en este juego de pruebas supondremos structs generados sólo de tipos básicos, pero en el siguiente juego de pruebas esto ya no será ası́. Los structs los construiremos en una reserva de auxspace, como si del valor de retorno de una función se tratase. Como guı́a, la estructura del código intermedio generado deberı́a ser similar a la siguiente: Reservar espacio en el auxspace; podéis saber cuanto ocupará el struct si consultáis el campo size del ptype asociado al nodo del AST correspondiente a <<>>. Para cada expresión encontrada dentro de <<>>: • Evaluar la expresión (código generado mediante GenRight) • Calcular la posición de memoria donde deberı́a ir este miembro del struct que estamos generando. Podéis usar el siguiente fragmento de código para obtener información sobre el offset (eoff) del miembro actual dentro del struct y su tipo (etp). Igual que antes, 4 suponiendo que a contiene el nodo del AST correspondiente a OPENANGL (y por lo tanto que a->tp contiene el tipo del struct a empaquetar) y que i es el número de la expresión actual. string s = "e" + itostring(i); ptype etp = a->tp->struct_field[s]; int eoff = a->tp->offset[s]; La posición de memoria será igual al inicio del struct en el auxspace sumado al offset del miembro al que estáis escribiendo. • Guardar el resultado de la evaluación de la expresión en la posición de memoria correspondiente. (1 punto) Y ası́ podréis superar el tercer juego de pruebas. 1: Program 2: Vars 3: a Struct 4: v Int 5: EndStruct 6: b Struct 7: v Int 8: c Int 9: EndStruct 10: c Struct 11: x Bool 12: a Int 13: b Int 14: c Int 15: EndStruct 16: x Int 17: v Bool 18: EndVars 19: 20: a := << 3 >> 21: b := << 1, 1 + 1 >> 22: c := << 1 = 1, 1, 1, 1 >> 23: 24: WriteLn(a.v) 25: Write(b.v) Write(" ") WriteLn(b.c) 26: If Not c.x Then 27: WriteLn("Fallo") 28: EndIf 29: x := c.a + c.b + c.c 30: If Not (x = 3) Then 31: WriteLn("Fallo") 32: EndIf 33: EndProgram Executing code: 3 1 2 Ahora falta poder construir structs que contengan arrays y otros structs, incluso generados con <<>>. En principio, sólo deberı́ais tener que alterar la generación de código para distinguir cuando el tipo de una de las expresiones dentro de <<>> es básico o no (este tipo lo tenéis en etp si miráis la guı́a del apartado anterior), y actuar en consecuencia, decidiendo si hay que copiar memoria o no y cuanta. Podéis mirar cómo lo hace el operador de asignación. (0,75 puntos) Con esto podréis superar el cuarto juego de pruebas. 5 1: Program 2: Vars 3: a Struct 4: b Struct 5: c Int 6: d Bool 7: EndStruct 8: e Struct 9: f Int 10: g Bool 11: EndStruct 12: EndStruct 13: h Struct 14: i Int 15: j Bool 16: EndStruct 17: x Int 18: v Bool 19: EndVars 20: 21: h := << 2, True >> 22: a := << << 3, True >>, h >> 23: 24: WriteLn(a.b.c) 25: WriteLn(a.e.f) 26: If Not a.b.d Or Not a.e.g Then 27: WriteLn("Error") 28: EndIf 29: EndProgram Executing code: 3 2 Parte II En la segunda parte, queremos introducir un nuevo tipo de asignación, que llamaremos de desempaquetado, que, dada una serie de expresiones referenciables en la parte izquierda y un struct en la parte derecha, intentará asignar el primer miembro del struct a la primera variable en la parte izquierda y ası́ sucesivamente. La sintaxis de esta nueva instrucción será: << e1, e2, ... >> := s Donde e1, e2, ... son expresiones referenciables (para simplificar, sólo serán posible identificadores en el primer juego de pruebas), separadas por comas. No presenta ambigüedades con la asignación normal, pues toda instrucción de asignación de éste tipo comienza por el token OPENANGL. Os sugerimos que diseñéis la gramática de forma que ASIG sea el nodo raı́z del arbol sintáctico generado por esta nueva instrucción de asignación, pero que como primer hijo tenga al token OPENANGL como padre de toda la lista de expresiones referenciables. Como parte del análisis semántico, tendréis que distinguir si se trata de un caso de asignación normal o bien de desempaquetado (lo sabréis porque el primer hijo de la desempaquetado es un 6 nodo OPENANGL), y en este último caso verificar si lo que hay en la parte derecha de la asignación es un struct, y dar un error en caso contrario. También, deberéis verificar que el número de variables que hay en la parte izquierda de la asignación es igual al número de miembros que tiene el struct de la parte derecha (ésta última cuenta la podéis obtener, suponiendo que stp es el ptype correspondiente al struct de la parte derecha, usando stp->ids.size()). Disponéis, en el archivo newsemanticerrors.cc los diversos mensajes de error que debéis dar en este y el siguiente juego de pruebas, listos para copiar a vuestro semantic.cc. (1,5 puntos) Con esto podréis pasar el quinto juego de pruebas: 1: Program 2: Vars 3: a Struct 4: b Int 5: c Bool 6: EndStruct 7: x Int 8: y Bool 9: z Int 10: EndVars 11: 12: << x, y >> := a 13: << x, y >> := z 14: 15: << x >> := a 16: << x, y, z >> := a 17: EndProgram Type Checking: L. 13: Unpacking assignment requires a struct. L. 15: The number of items in the unpacking assignment do not match. L. 16: The number of items in the unpacking assignment do not match. El siguiente juego de pruebas ya permite cualquier tipo de expresión referenciable como parte izquierda (las mismas que permite el operador de asignación) y añade, como verificaciones semánticas: Que las expresiones en las partes izquierdas sean referenciables. Que los tipos de las expresiones o variables en la parte izquierda sean compatibles con los tipos de los miembros del struct en la parte derecha (excepto si el tipo de la expresión es error). Si el número de expresiones a la izquierda no coincide con el número de miembros del struct de la derecha, no verificar nada de lo anterior. Para la segunda verificación necesitaréis recorrer la lista de miembros de un struct. Aquı́ tenéis una manera de hacerlo (suponiendo como antes que stp contiene el ptype del struct): list<string>::iterator it = stp->ids.begin(); while (it != stp->ids.end()) { ptype etp = stp->struct_field[*it]; // etp es el tipo de este elemento: procesarlo. it++; } 7 Deberéis modificar el bucle anterior para recorrer, además de la lista de miembros del struct, la lista de expresiones en la parte izquierda del árbol sintáctico y ir comparando tipos (parecido a como recorréis las listas de parámetros formales y reales a la vez). (1 punto) Haciendo esto podréis pasar el sexto juego de pruebas. 1: Program 2: Vars 3: a Struct 4: b Bool 5: c Int 6: EndStruct 7: d Struct 8: e Array[1] Of 9: g Array[1] Of 10: EndStruct 11: x Int 12: y Bool 13: EndVars 14: 15: << x, y >> := a 16: << y, x >> := a 17: << x, x >> := a 18: 19: << d.e[1], d.g[1] 20: << d.g[1], d.e[1] 21: 22: z := 1 23: d.z := 2 24: << z, d.z >> := a 25: EndProgram Int Bool >> := a >> := a Type Checking: L. L. L. L. L. L. L. L. L. L. L. 15: 15: 17: 19: 19: 22: 22: 23: 24: 24: 24: Unpacking assignment with incompatible types for item 1. Unpacking assignment with incompatible types for item 2. Unpacking assignment with incompatible types for item 1. Unpacking assignment with incompatible types for item 1. Unpacking assignment with incompatible types for item 2. Identifier z is undeclared. Left expression of assignment is not referenceable. Field z is not defined in the struct. Identifier z is undeclared. Left expression of item 1 in unpacking assignment is not referenceable. Field z is not defined in the struct. Llegamos a la generación de código del operador de desempaquetado y a la última parte de la modificación. El código generado para esta instrucción deberá primero obtener la dirección de la parte derecha de la asignación (recordad que puede ser una función, y que no podéis invocarla más de una vez) y guardarla en un temporal. Luego, ir iterando sobre la lista de miembros del struct, y para cada uno: obtener la referencia a la variable donde irá desempaquetado, calcular la dirección al miembro del struct donde desempaquetaréis, y copiar (el tamaño a copiar lo podéis sacar del ptype de cada miembro, etp). Veréis que, de esta forma, no hace falta distinguir si el tipo de cada miembro es básico o no. Por simplicidad, no consideraremos el caso en el que un struct es desempaquetado, total o parcialmente, sobre sı́ mismo. 8 (1 punto) Ası́ podréis pasar el séptimo juego de pruebas. 1: Program 2: Vars 3: a Struct 4: b Bool 5: c Int 6: EndStruct 7: d Array[1] Of Bool 8: e Struct 9: f Int 10: g Int 11: EndStruct 12: h Array[2] Of Int 13: i Struct 14: j Array[2] Of Int 15: k Struct 16: l Int 17: m Int 18: EndStruct 19: EndStruct 20: x Bool 21: y Int 22: EndVars 23: 24: Function r(Ref n Struct f Int g Int EndStruct) 25: Return Struct f Int g Int EndStruct 26: Write("r: ") WriteLn(n.f) 27: Return n 28: EndFunction 29: 30: a.b := True 31: a.c := 4 32: e.f := 5 33: 34: << x, y >> := a 35: Write("x: ") WriteLn(x) 36: If Not x Then WriteLn("Error") EndIf 37: 38: << d[0], e.g >> := a 39: Write("e: ") WriteLn(e.g) 40: If Not d[0] Then WriteLn("Error") EndIf 41: 42: << h[0], h[1] >> := r(e) 43: Write("h: ") Write(h[0]) Write(" ") WriteLn(h[1]) 44: 45: i.j[0] := 6 46: i.j[1] := 7 47: i.k.l := 8 48: i.k.m := 9 49: << h, e >> := i 50: Write("h2:") Write(h[0]) Write(" ") WriteLn(h[1]) 51: Write("e2:") Write(e.f) Write(" ") WriteLn(e.g) 52: EndProgram Executing code: x: 1 e: 4 r: 5 h: 5 4 h2:6 7 9 e2:8 9 (0,5 puntos) Combinando parte 1 y parte 2 deberı́ais ser capaces de pasar el último juego de pruebas. 1: Program 2: Vars 3: min Int 4: max Int 5: EndVars 6: 7: Function MinMax(Val a Int, Val b Int) Return Struct min Int max Int EndStruct 8: Vars 9: min Int 10: max Int 11: EndVars 12: If a > b Then min := b max := a 13: Else min := a max := b 14: EndIf 15: Return << min, max >> 16: EndFunction 17: 18: <<min, max>> := <<10, 10 * 2>> 19: Write(min) Write(" ") WriteLn(max) 20: <<min, max>> := MinMax(13, 16) 21: Write(min) Write(" ") WriteLn(max) 22: <<min, max>> := MinMax(20, 5) 23: Write(min) Write(" ") WriteLn(max) 24: <<min, max>> := <<max, min>> 25: Write(min) Write(" ") WriteLn(max) 26: EndProgram Executing code: 10 20 13 16 5 20 20 5 Autoevaluación: Basta con ejecutar ./fesentrega.sh para crear automáticamente un fichero llamado entrega.tar. Si ejecutáis entonces ./checker.sh, al cabo de un rato os indicará qué juegos de pruebas habéis pasado junto con la correspondiente nota final. La evaluación definitiva se hará con ligeras modificaciones de dichos juegos de pruebas. De hecho, el checker es una ayuda bastante fidedigna para comprobar que vuestras modificaciones funcionan, pero queda bajo vuestra responsabilidad el comprobar que emitı́s los errores esperados y que no dais errores absurdos de más. Entrega: Tenéis dos opciones. Podéis ejecutar perl entregador.pl, que realiza la entrega del fichero entrega.tar del mismo directorio. También podéis conectaros en prácticas via web a examens.fib.upc.edu. Debéis entregar el fichero entrega.tar creado tal y como se indica en la autoevaluación. 10