Modificación de la Práctica de CL

Anuncio
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
Descargar