Especificación del lenguaje PascUned
Especificación del lenguaje PascUned
Práctica de Procesadores
del Lenguaje II
Especificación del lenguaje PascUned
1 Introducción ........................................................................................................................... 4
2 Descripción del lenguaje ........................................................................................................ 4
2.1 Aspectos Léxicos ............................................................................................................. 4
2
3.2.1 Fechas y forma de entrega .................................................................................... 36
3.2.3.5 Dificultades..................................................................................................... 39
4 Herramientas ....................................................................................................................... 40
4.1 JFlex .............................................................................................................................. 40
3
1 Introducción
En este documento se define la práctica de la asignatura Procesadores del Lenguaje II
correspondiente al curso 2014/2015. El objetivo de la práctica es realizar un compilador del
lenguaje PascUned, variación del lenguaje de programación Pascal, en sus fases de análisis
semántico, generación de código intermedio y generación de código final.
Primero se presenta una descripción del lenguaje elegido y las características especiales que
tiene. A continuación se indicará el trabajo a realizar por los alumnos en diferentes fases,
junto con las herramientas a utilizar para su realización.
Desde el punto de vista léxico, un programa es una secuencia ordenada de TOKENS. Un TOKEN
es una entidad léxica indivisible que tiene un sentido único dentro del lenguaje. En términos
generales es posible distinguir diferentes tipos de TOKENS: los operadores aritméticos,
relacionales y lógicos, los delimitadores como los paréntesis o los corchetes, los identificadores
utilizados para nombrar variables, constantes o nombres de procedimientos, o las palabras
reservadas del lenguaje son algunos ejemplos significativos. A lo largo de esta sección
describiremos en detalle cada uno de estos tipos junto con otros elementos que deben ser
tratados por la fase de análisis léxico de un compilador.
2.1.1 Comentarios
Un comentario es una secuencia de caracteres que se encuentra encerrada entre los
delimitadores de principio de comentario y final de comentario: “{” y “}”, respectivamente.
Todos los caracteres encerrados dentro de un comentario deben ser ignorados por el
analizador léxico. En este sentido su procesamiento no debe generar TOKENS que se
comuniquen a las fases posteriores del compilador. En nuestra versión de Pascal no es posible
4
realizar anidamiento de comentarios. De esta manera, dentro de un comentario no pueden
aparecer los delimitadores de comentario “{” y “}” para acotar el comentario anidado.
Otra posibilidad de nuestro lenguaje es declarar un comentario de una línea con el delimitador
“//”. En este caso todos los caracteres comprendidos entre “//” y un salto de línea serán
ignorados con la excepción de “{“ y “}“, que de encontrarse deberá generar un error léxico
{ Comentario Anidado 1 }
Lógicas. Las constantes lógicas representan valores de verdad (cierto o falso) que son
utilizados dentro de expresiones lógicas como se verá más adelante. Únicamente
5
existen 2 que quedan representadas por las palabras reservadas true y false e indican
el valor cierto y falso respectivamente
2.1.3 Identificadores
Un identificador consiste, desde el punto de vista léxico, en una secuencia ordenada de
caracteres y dígitos que comienzan obligatoriamente por una letra. Los identificadores se usan
para nombrar entidades del programa tales como las variables o los subprogramas definidos
por el programador. El lenguaje no es sensible a las mayúsculas (case sensitive), lo que significa
que dos identificadores compuestos de los mismos caracteres y que difieran únicamente en el
uso de mayúsculas o minúsculas se consideran iguales. Por ejemplo, Abc y ABC son
identificadores iguales. La longitud de los identificadores no está restringida.
A continuación se muestra una tabla con las palabras reservadas del lenguaje así como una
breve descripción aclarativa de las mismas. Su uso se verá en más profundidad en los
siguientes apartados.
and Y lógica
6
end Final de bloque de sentencias
of Declaración de conjuntos
or O lógica
7
write Procedimiento predefinido que muestra
por pantalla un entero o cadena de texto
(Sin salto de línea)
2.1.5 Delimitadores
El lenguaje define una colección de delimitadores que se utilizan en diferentes contextos. A
continuación ofrecemos una relación de cada uno de ellos:
DELIMITADOR DESCRIPCIÓN
[ ] Delimitadores de rango
{ } Delimitadores de comentario
2.1.6 Operadores
Existen diferentes tipos de operadores que son utilizados para construir expresiones por
combinación de otras más sencillas como se discutirá más adelante. En concreto podemos
distinguir los siguientes tipos:
8
+ (suma aritmética, unión de conjuntos)
Operadores aritméticos
- (resta aritmética)
< (menor)
Operadores relacionales
> (mayor)
= (igual)
<> (distinto)
:= (asignación)
Operadores de asignación
= (definición de constantes simbólicas)
IN (pertenencia a un conjunto)
Operadores especiales
^ (operador de indirección. Acceso al contenido
apuntado por un puntero)
Obsérvese que, como se advirtió con anterioridad, no se consideran los operadores unarios +
y –, de forma que los literales numéricos que aparezcan en los programas serán siempre sin
signo (positivos). Así, los números negativos no aparecerán en el lenguaje fuente, pero sí
pueden surgir en tiempo de ejecución como resultado de evaluar una expresión aritmética de
resultado negativo, como por ejemplo 1-4.
9
2.2.1 Estructura de un programa y ámbitos de visibilidad
Un programa en PascUned es un fichero de código fuente con extensión ‘.p’ que tiene la
siguiente estructura sintáctica:
program <nombre_del_programa>;
[ const
<declaraciones_de_constantes globales> ]
[ type
<declaraciones_de_tipos_compuestos globales> ]
[ var
<declaraciones_de_variables globales> ]
[ <declaraciones_de_subprogramas> ]
begin
<bloque_de_instrucciones>
end.
Las partes encerradas entre corchetes son opcionales. Es decir, pueden no estar presentes en
el código.
var a:integer;
var b:integer;
var
a:integer;
b:integer;
10
palabras reservadas begin y end. Nótese que un programa ha de terminar con punto “.” Y no
con punto y coma “;” como los subprogramas.
Referencias locales. Los identificadores son locales a un ámbito cuando son solamente
accesibles desde dentro de dicho ámbito. Por ejemplo, todas las variables y tipos
definidos dentro de un subprograma (más los parámetros en el caso de los
subprogramas) son locales al mismo y sólo pueden ser accedidos por el código del
bloque.
program ejemploPascUned;
const
cierto = true;
falso = false;
type
usuario = record
dni : integer;
edad : integer;
11
casado : boolean;
end;
var
{Declaración de subprogramas }
procedure imprimeCasado(valor:boolean);
begin
write(“casado”);
else
write(“soltero”);
end;
begin
end;
begin
12
else
end;
begin
if (mayorDeEdad(usuario1.edad))then
begin
write(“usuario:”);
end;
end;
En esta práctica las constantes simbólicas pueden ser de tipo entero positivo o lógico
(definidas en el siguiente apartado). La sintaxis para la declaración de constantes simbólicas
enteras es la siguiente:
nombre = constanteLiteral;
Donde nombre es el nombre simbólico que recibe la constante definida dentro del programa
y constanteLiteral un valor constante literal de tipo entero positivo (número sin signo)
o lógico (true o false) de manera que cada vez que éste aparece referenciado dentro del
código se sustituye por su valor establecido en la declaración. La palabra reservada const
13
marca el inicio de la sección de declaraciones de constantes y sólo puede aparecer una vez
dentro de cada ámbito.
const
uno = 1;
dos = 2;
cierto = true;
Desde el punto de vista sintáctico, el tipo entero se representa con la palabra reservada
integer. La aplicación de este tipo aparece en distintos contextos de uso como en la
declaración de variables, la declaración de parámetros de funciones o los tipos de retorno de
las mismas (véase más adelante).
var
a, b : integer;
14
Tipo lógico (boolean)
El tipo lógico representa valores de verdad, representados por las constantes literales true y
false. Para referirse en PascUned a este tipo de datos se utiliza la palabra reservada boolean. A
continuación se muestran unos ejemplos de declaración de tipo lógico.
var
esCierto:boolean;
Tanto en el caso de tipos enteros o lógicos los datos tendrán un tamaño de memoria de una
palabra (16 bits) dentro del entorno de ejecución ENS2001.
Tipo puntero
Una variable de tipo puntero (también llamada simplemente puntero) almacenará la dirección
en memoria de otra variable. Otra forma de describirlo es diciendo que un puntero “apunta” a
la posición de memoria de una variable. En general, los punteros se tipifican para indicar el
tipo de datos de la variable a las que apuntan. Sin embargo, en PascUned los punteros pueden
apuntar solo a variables de tipo entero. Esta es la razón por la cual consideraremos a los
punteros a enteros, tipos primitivos de este lenguaje. Lo que significa que NO pueden aparecer
definidos en la sección type (ver apartado de tipos compuestos). Para representar el tipo
puntero se usa la construcción sintáctica ^integer. La sintaxis para declarar variables de tipo
puntero es:
nombreVariable : ^integer;
p:=@varEntero;
p:=q;
Para asignar un nuevo valor a la variable a la que apunta el puntero se utiliza la siguiente
sintaxis:
p^:= expEntera;
15
Dónde expEntera es una expresión de tipo entero. Hay que destacar que esta expresión puede
ser la dirección de memoria de una variable, ya que no deja de ser un valor entero. Sería válida
por ejemplo la siguiente sentencia: p^:=@var;. La expresión p^ devuelve el valor de la variable
a la que apunta el puntero. Es por tanto un valor entero. NO se debe considerar la posibilidad
de una doble indirección del tipo p^^. En ese caso se debe generar un error.
var
p, q :^integer;
a, b: integer;
begin
a:=3;
p:=@a;
q:=p;
a:=a+1;
end;
16
sección de declaración de variables. En su lugar hay que crear previamente un tipo
estructurado (con nombre) y usar dicho nombre después en la declaración de variables. En lo
que respecta al sistema de tipos, la equivalencia de éstos es nominal, no estructural. Es decir,
dos tipos serán equivalentes únicamente si tienen el mismo nombre. Los tipos compuestos los
define el usuario siempre en la sección type dentro de program o de un subprograma.
Donde <<definición de tipo>> es una expresión de tipo de las definidas a continuación. En este
sentido hay que precisar que: 1) no existen tipos sinónimos (por ejemplo, es un error TEntero =
integer), 2) cada tipo puede tener un único nombre (es un error Ta, Tb = <<expresión de
tipos>>) y 3) que no es posible definir tipos puntero (ya que como dijimos se consideran en
PascUned tipos primitivos). En concreto se distinguen dos tipos de datos compuestos en
PascUned: los conjuntos y los registros.
Tipo Conjunto
Las variables de tipo conjunto almacenan una colección de valores no repetidos que se
encuentran comprendidos dentro de un rango de valores enteros positivos. Por ejemplo, si
definimos un tipo conjunto con rango 1..3, los valores posible que pueden tomar las variables
de ese tipo son: el 1, el 2, el 3, el 1 y el 2, el 1 y el 3, el 2 y el 3, el 1 el 2 y el 3, o el conjunto
vacio (sin elementos). La sintaxis para declarar un tipo conjunto es:
type
17
Tras declarar una variable de tipo conjunto (ver apartado de declaraciones de variables) se le
pueden asignar valores con la siguiente sintaxis:
varSet := [];
En esta definición los corchetes no significan que se trata de una parte opcional en el código.
Son delimitadores para declarar un rango (Importante: no aparecen en la definición del tipo).
En este caso valorIncial y valorFinal son de tipo entero, pudiendo ser expresiones, resultado de
llamadas a función, etc. Por ejemplo, suponiendo una variable s de tipo unoAdiez del listado
anterior, la sentencia s:= [2..5]; almacenaría en la variable s el rango de números
comprendidos entre 2 y 5, es decir: 2,3,4 y 5. En caso de intentar asignar un rango que no esté
en los valores del tipo definido se ha de generar un error y terminar el programa. Esta
comprobación debería realizarse en tiempo de ejecución, por lo tanto ha de implementarse en
el código final. La segunda forma indicada se corresponde con la asignación del conjunto vacío.
En este caso varSet no contendría ningún valor.
No se pueden asignar otros tipos que no sean una expresión de rango. Por ejemplo, intentar
asignar una variable de tipo entero o booleano a una variable de tipo conjunto debe generar
un error de tipos. Tampoco se puede asignar un conjunto a otro conjunto (excepto con la
unión). Serían errores también asignaciones de la forma: s:=[1,2,3] s:=[1]. En caso de querer
asignar un solo valor sería de la forma [1..1].
A continuación se van a explicar en este apartado varias operaciones que se pueden realizar
sobre variables de tipo conjunto, como son la pertenencia y la unión.
Pertenencia. Esta operación permite saber si un determinado valor entero se encuentra entre
los valores asignados a una variable de tipo conjunto. La sintaxis es:
valorEntero IN variableTipoConjunto
valorEntero representa un valor de tipo entero, pudiendo ser una expresión, constante
literal o simbólica, etc;. variableTipoConjunto representa a una variable de tipo conjunto.
Esta expresión devuelve un valor booleano que es true en caso de que el valorEntero esté
dentro del conjunto. Siguiendo con el ejemplo anterior de la variable s, en caso de tener: “3 IN
s”, devolvería true, mientras que “8 IN s” devolvería false. Debido a este comportamiento lo
más habitual es que este tipo de expresiones aparezcan en sentencias de tipo IF o DO UNTIL,
que se explicarán más adelante.
Unión. Esta operación realiza la unión de dos o más conjuntos. Es decir, el conjunto resultado
contiene todos los elementos de los conjuntos que se “unen”. La sintaxis es:
Los corchetes indican partes opcionales en el código. En el caso de esta práctica, todos los
operandos han de ser variables de tipo conjunto. No es posible realizar la unión de rangos de
18
valores ni la unión de una variable con un rango. En concreto todas las variables tanto de la
expresión como de la asignación han de ser del mismo tipo de conjunto.
En la unión de conjuntos hay que recordar que en caso de existir dos elementos iguales sólo se
incluye una vez en el conjunto resultado. En el siguiente listado se puede observar un ejemplo.
type
var
begin
c2:=[2..4];
c3:=[3..5];
c1:= c2+c3;
c2:=[7..10];
c4:= c2+c3;
c6:=[2..3];
C5:=c2+c6;
end;
Tipo Registro
Un registro es una estructura de datos compuesta que permite agrupar elementos de
diferentes tipos. Para definir un registro se utiliza la palabra reservada record seguida de una
lista de declaraciones de campos y termina con la palabra reservada end;. Cada declaración de
campos consiste en un nombre (identificador) seguido del delimitador de tipo seguido de un
tipo primitivo. Concretamente la declaración de un registro sigue la siguiente sintaxis:
19
nombreTipoRegistro = record
campo1 : TipoPrimitivo;
campo2 : TipoPrimitivo;
...
campoN : TipoPrimitivo;
end;
type
tipoPersona = record
dni : integer;
casado: boolean;
end;
var
begin
persona.dni := 123;
persona.casado := true;
write(persona.dni);
end;
20
nombre1 [, nombre2, nombre3...] : tipo;
Donde tipo es el nombre de un tipo primitivo del lenguaje o definido por el usuario,
nombre1, nombre2,... son identificadores para las variables creadas de ese tipo. Como se
ve en la sintaxis, si se desean declarar varias variables de un mismo tipo en la misma línea ha
de hacerse utilizando el delimitador “,”.
type
var
a:integer;
a,b:integer;
x,y:boolean;
p,q:^integer;
conjunto: tipoConjunto;
2.2.5.1 Procedimientos
Los procedimientos son rutinas encapsuladas bajo un nombre que realizan una determinada
operación para que el programador las invoque, convenientemente parametrizadas, desde
distintos puntos del programa. La sintaxis de un procedimiento es:
...
[ const
<declaraciones_de_constantes globales> ]
[ type
<declaraciones_de_tipos_de_datos globales> ]
21
[ var
<declaraciones_de_variables globales> ]
[ <declaraciones_de_subprogramas> ]
begin
<bloque_de_instrucciones>
end;
En esta definición los corchetes indican código opcional. Donde nombre es el nombre del
procedimiento, paramIJ el nombre de cada uno de los parámetros y tipo1, tipo2,... tipoM los
tipos de dichos parámetros. Como puede verse, la declaración de los parámetros formales es
una lista de declaraciones de parámetros separadas por un delimitador de punto y coma. Cada
declaración de parámetros tiene la forma de una lista de identificadores separados por comas,
seguido del delimitador de tipo, dos puntos (:) y seguido de un nombre de tipo primitivo o un
identificador de tipo compuesto. La forma de pasar parámetros es posicional y será explicada
en este apartado más adelante. La declaración de parámetros es opcional. La cabecera de un
procedimiento sin parámetros tiene esta estructura sintáctica:
procedure nombre();
Por su parte, la cabecera va seguida de un cuerpo con una estructura potencialmente idéntica
a la de program (sección const, type y var, en este orden, subprogramas, y sentencias
encerradas entre los delimitadores begin y end;). En el siguiente listado presentamos un
ejemplo de procedimiento.
const uno = 1;
var y:integer;
begin
write(z);
end;
begin
x := x + uno;
escribe(x);
end;
22
2.2.5.2 Funciones
Las funciones son rutinas encapsuladas bajo un nombre que realizan un determinado cómputo
y cuyo resultado devuelven al contexto de invocación. Como discutiremos más adelante, dicho
contexto suele ser una expresión. Desde el punto de vista sintáctico, una función sólo se
diferencia de un procedimiento en dos aspectos: 1) después de la declaración de los
parámetros le sigue un delimitador de tipo, dos puntos (:), seguido de un tipo primitivo
(incluidos punteros) (las funciones NO pueden devolver estructuras de datos como resultado
de su invocación) y 2) El valor de retorno es una variable con el nombre de la función. Esta
variable no ha de ser declarada en la sección de declaración de variables de la función. Si se
declarara una con el mismo nombre debería darse un error. Es obligatorio que exista, al
menos una, una sentencia de asignación a la variable de retorno en el cuerpo de una función.
En caso de no existir debe generarse un error. La sintaxis de la cabecera en la declaración de
una función es la siguiente:
...
Donde nombre es el nombre de la función, paramIJ el nombre de cada uno de los parámetros,
tipo1, tipo2,... tipoM los tipos de dichos parámetros y tipo, el tipo de retorno. Como en el caso
de los procedimientos, el uso de parámetros en una función es opcional, así como la
declaración de parámetros.
begin
suma:=x+y;
end;
const numUno = 1;
begin
uno:= numUno;
end;
function cierto:boolean;
begin
cierto:=true;
23
end;
begin
x:=x-y;
end;
var resta2;
begin
resta2:=x-y;
end;
En el ejemplo anterior se puede ver que las últimas dos funciones son erróneas. En el caso de
resta, no existe instrucción de retorno y en el caso de resta2, declara una variable (resta2) que
colisiona con el nombre de la función.
Paso por valor. En este caso el compilador realiza una copia del argumento a otra zona
de memoria para que el subprograma pueda trabajar con él sin modificar el valor del
argumento tras la ejecución de la invocación. Los parámetros actuales pasados por
valor no pueden ser conjuntos ni registros completos.
Paso por referencia. En este caso, el parámetro sólo podrá ser una referencia (no una
expresión) y el compilador transmite al subprograma la dirección de memoria donde
está almacenado el parámetro actual, de forma que las modificaciones que se hagan
dentro de éste tendrán efecto sobre el argumento una vez terminada la ejecución del
mismo. El paso de parámetros por referencia es utilizado para pasar argumentos de
salida o de entrada / salida. Para indicar que un parámetro se pasa por referencia debe
precederse su nombre con la palabra reservada var. Se considera un error de
24
compilación pasar expresiones como argumentos actuales a una función donde se ha
declarado un parámetro formal de salida o entrada/salida.
En el siguiente listado se incluyen ejemplos de subprogramas que utilizan paso por valor y por
referencia:
var a,b:integer;
begin
incrementa:=x+1;
end;
begin
y:=y+1;
end;
begin
a := 1;
b := incrementa (a); { a = 1, b = 2)
incrementaVar (a); {a = 2}
end;
Es muy importante destacar que se debe dar soporte a la recursividad directa. Es decir, dentro
del código de un subprograma puede aparecer una llamada a si mismo provocando una nueva
ejecución de su código. Sin embargo, la recursividad indirecta (aquellos casos en que un
subprograma llama a otro y éste al primero) no es preciso contemplarla.
25
2.2.6.1 Expresiones
Una expresión es una construcción del lenguaje que devuelve un valor de retorno al contexto
del programa donde aparece la expresión. Las expresiones no deben aparecer de forma
aislada en el código. Es decir, han de estar incluidas como parte de una sentencia allá donde se
espere una expresión. Desde un punto de vista conceptual es posible clasificar las expresiones
de un programa en varios grupos:
Expresiones aritméticas
Las expresiones aritméticas son aquellas cuyo cómputo devuelve un valor de tipo entero al
programa. Puede afirmarse que son expresiones aritméticas las constantes literales de tipo
entero, las constantes simbólicas de tipo entero, los identificadores (variables o parámetros)
de tipo entero y las funciones que devuelven un valor de tipo entero. Asimismo también son
expresiones aritméticas la suma y resta de dos expresiones aritméticas. El operador @
también definirá una expresión aritmética, ya que devuelve un entero que representa la
dirección de memoria de la variable a la que es aplicado. De la misma forma lo es el operador
^, ya que devolverá el contenido de la variable entera a la que apunta el puntero.
Expresiones lógicas
Las expresiones lógicas son aquellas cuyo cómputo devuelve un valor de tipo lógico al
programa. Sintácticamente puede afirmarse que son expresiones lógicas las constantes
literales de tipo lógico (valores de verdad true y false), las constantes simbólicas de tipo lógico,
los identificadores (variables o parámetros) de tipo lógico y las funciones que devuelven un
valor de tipo lógico. Asimismo también son expresiones lógicas la conjunción (and) y
disyunción (or) de dos expresiones aritméticas. En PascUned se incluyen una serie de
operadores relacionales que permite comparar expresiones aritméticas entre sí. El resultado
de esa comparación es también una expresión lógica. Es decir, la comparación con los
operadores >, <, = o <> de dos expresiones aritméticas es también una expresión lógica.
Asimismo, la pertenencia a conjuntos (in) se considera una expresión lógica ya que devuelve
un valor lógico.
Precedencia Asociatividad
. ( ) Izquierdas
and or Izquierdas
+ - Izquierdas
26
Para alterar el orden de evaluación de las operaciones en una expresión aritmética prescrita
por las reglas de prelación se puede hacer uso de los paréntesis. Como puede apreciarse los
paréntesis son los operadores de mayor precedencia. Esto implica que toda expresión
aritmética encerrada entre paréntesis es también una expresión aritmética. En el siguiente
listado se exponen algunos ejemplos sobre precedencia y asociatividad y cómo la
parentización puede alterar la misma. En el caso de la pertenencia a conjuntos, IN irá siempre
entre paréntesis, separándose de otras expresiones lógicas. El siguiente listado muestra
algunos ejemplos.
Llamadas a función
Para llamar a una función ha de escribirse su identificador indicando entre paréntesis los
parámetros actuales de la llamada, que deben coincidir en número y tipo con los definidos en
la declaración del subprograma. Los parámetros pueden ser referencias a variables, campos de
registros, constantes o expresiones (ver apartado 2.2.5.3).
27
El uso de los paréntesis es siempre obligatorio. Así, si una función no tiene argumentos, en su
llamada es necesario colocar unos paréntesis vacios () tras su identificador.
Nótese que las llamadas a funciones actúan como expresiones, pero las llamadas a
procedimientos son sentencias y nunca una expresión. Por tanto, si se utilizan llamadas a
funciones en el lugar de una sentencia, debe generarse un error. Esta comprobación es propia
del análisis semántico.
x:=suma(a, b);
a := funcion1();
Evaluación de expresiones
El modelo de evaluación de expresiones lógicas de PascUned es en cortocircuito o modo de
evaluación perezosa. Este modo de evaluación prescribe que: 1) si en la evaluación de una
conjunción (and) de dos expresiones la primera de las evaluadas es falsa, la otra no debe
evaluarse y 2) si en la evaluación de una disyunción (or) de dos expresiones la primera de las
evaluadas es verdadera, la otra no debe evaluarse.
2.2.6.2 Sentencias
PascUned dispone de una serie de sentencias que permiten realizar determinadas operaciones
dentro del flujo de ejecución de un programa. En concreto nos estamos refiriendo a las
sentencias de asignación, las sentencias de control de flujo condicional e iterativo, y las
sentencias de entrada salida.
Asignaciones
Las instrucciones de asignación sirven para asignar un valor a una variable, puntero, conjunto o
campo de un registro. Para ello se escribe primero una referencia a alguno de estos elementos
seguido del operador de asignación “:=” y a su derecha una expresión. El compilador deberá
comprobar en primer lugar la compatibilidad entre el tipo de la expresión a la derecha del
operador de asignación y el de la referencia a la izquierda. El operador de asignación no es
asociativo. Es decir, no es posible escribir construcciones sintácticas del estilo a := b := c. La
sintaxis corresponde a:
ref := expresion;
28
Según el tipo de la variable ref, la expresión deberá ser compatible con ese tipo. Por ejemplo,
variables lógicas sólo admiten valores lógicos mientras que conjuntos sólo admiten rangos o
uniones de conjuntos. El siguiente listado muestra algunos ejemplos de uso de sentencias de
asignación:
i := 3 + 7;
distinto := 3<>4;
juan.casado = false;
a = 2 + suma(2,2);
conjunto:= [2..3];
p:=@a;
p^:=3+b;
En PascUned no es posible hacer asignaciones a estructuras de forma directa. Así son errores
de compilación sentencias como registro1 := registro2; y conjunto1:=conjunto2;
if (expresiónLogica) then
<<sentencias>>
[else
<<sentencias>>]
Donde expresionLogica es una expresión lógica que se evalúa para comprobar si deben
ejecutarse las sentencias o bloque de sentencias. La parte del else es opcional. Es decir, puede
o no aparecer al escribir esta sentencia. Es necesario que expresionLogica esté siempre
entre paréntesis. <<sentencias>> puede corresponder a una sentencia o a varias. En caso de
que sentencias esté compuesta por varias sentencias, se ha de declarar un bloque con begin y
end;. Esto no es necesario (aunque posible) si sólo existe una sentencia. El uso del punto y
coma es obligatorio siempre tras la palabra reservada end.
29
Listado 17. Ejemplos de sentencia if – then -else
if (a=b) then
a:=a+1;
if (a=b) then
begin
a:=a+1;
end;
{varias sentencias}
if (a=b) then
begin
a:=a+1;
b:=b+1;
end;
{uso de else}
if (esCierto=true) then
valor:=true;
else
valor:=false;
if (esCierto=true)then
begin
valor:=true;
a:=a+1;
end;
else
30
valor:=false;
if (a in set) then
else
Este tipo de construcciones pueden anidarse con otras construcciones de tipo if-then-else o
con otros tipos de sentencias de control de flujo que estudiaremos a continuación.
repeat
<<sentencias>>
until (condicionLogica);
repeat
a:=a+1;
until (a>10);
repeat
a:=a+1;
b:=b+2;
until (a>10);
La sentencia repeat permite que otras estructuras de control formen parte del bloque de
sentencias a iterar de forma que se creen anidamientos. Hay que tener en cuenta también que
no se usan begin ni end para delimitar un bloque de sentencias.
31
Sentencia de control de flujo iterativo for
La sentencia for es otra sentencia de control de flujo iterativo funcionalmente similar a la
sentencia repeat. La estructura sintáctica de una sentencia for es:
<<sentencias>>
Donde variable es el índice que regula la iteración, valor1 indica el valor inicial del índice y
valor2 el final. Es decir, cuando la variable alcance valor2 se saldrá del bucle. Ambos valores
han de ser de tipo entero y pueden ser expresiones numéricas. Variable ha de ser de tipo
entero y no puede ser una expresión. Los paréntesis entre las palabras reservadas for y do son
obligatorios. No es necesario que se realice la comprobación de que valor1 es menor que
valor2.
Hay que hacer notar que no es posible declarar la variable índice dentro de la sentencia for.
Como se ha repetido numerosas ocasiones la declaración de variables ha de ser anterior a la
de las sentencias. En cada iteración del bucle variable incrementará su valor en uno de forma
automática. No existe por tanto la necesidad de explicitarlo con una sentencia de asignación
dentro del bucle. <<sentencias>> sigue las mismas reglas detalladas en el caso de if.
for (a:=0 to 5) do
b:=b+1;
x:= 2;
for (a:=0 to x) do
begin
b:=b+1;
c:=c+1;
end;
La estructura for permite que otras estructuras de control formen parte del bloque de
sentencia a iterar de forma que se creen anidamientos.
32
Sentencias de llamada a un procedimiento
Como ya se discutió con anterioridad, la invocación de procedimientos es una sentencia y no
una expresión. Por tanto, la llamada a un procedimiento no puede ubicarse en un contexto
sintáctico donde se espera una expresión, sino solamente donde se espera una sentencia. El
compilador debe detectar esta propiedad del subprograma y emitir un error en caso de que se
use una llamada a procedimiento donde se esperaba una función. La diferenciación entre las
llamadas a funciones y procedimientos suele delegarse al análisis semántico.
En caso de no recibir ningún parámetro no escribiría nada por pantalla, pero no sería
un error. Este procedimiento sólo admite un único parámetro y por último, indicar que
no se debe generar un salto de línea después de mostrar el resultado por pantalla (se
considerará un error en la práctica) ya que de eso se encargará la siguiente instrucción.
Como mínimo se exige que el compilador indique el tipo de error: léxico, sintáctico o
semántico. Los errores léxicos y sintácticos los genera la estructura proporcionada. Por lo
demás, se valorarán intentos de aportar más información sobre la naturaleza del error
33
semántico. Por ejemplo, los errores motivados por la comprobación explícita de tipos de
acuerdo al sistema de tipos de PascUned constituyen errores de carácter semántico. Algunos
otros ejemplos de errores semánticos son: identificador duplicado, tipo erróneo de variable,
variable no declarada, subprograma no definido, campo de registro inexistente, campo de
registro duplicado, demasiados parámetros en llamada a subprograma, etc.
Además, se proporciona una implementación del analizador léxico (fichero scanner.flex) y del
analizador sintáctico (fichero parser.cup) de la que se puede partir. No es obligatorio usar estas
implementaciones. Además, la implementación dada del análisis léxico y sintáctico se puede
modificar para adaptarla al desarrollo de la práctica que realice cada alumno. Para cada
especificación del lenguaje (ver siguiente apartado con la división del trabajo) se proporciona
una de estas implementaciones.
Es responsabilidad del alumno visitar con asiduidad el tablón de anuncios y los foros del
Curso Virtual, donde se publicarán posibles modificaciones a éste y otros documentos y
recursos.
Cada alumno deberá implementar solamente una de las dos especificaciones. La especificación
que debe realizar depende de su número de DNI. Asi:
34
El compilador debe respetar la descripción del lenguaje que se hace a lo largo de esta sección.
Incorporar características de Pascal no contempladas en PascUned no sólo no se valorará, sino
que se considerará un error y puede suponer un suspenso en la práctica.
A continuación se detallan las funcionalidades que incorporan cada una de las especificaciones
A y B. Para cada funcionalidad, la "X" indica que esa característica debe implementarse
mientras que el "-" indica que no debe implementarse.
FUNCIONALIDAD A B
Registros - X
Punteros - x
Por referencia X -
Operadores aritméticos + X -
- - X
> - X
= - X
<> X -
or - X
for - X
3.2 Entregas
Antes de empezar, nos remitimos al documento “Procesadores de Lenguajes II. Normas de la
asignatura” que podrá encontrar en el entorno virtual para más información sobre este tema.
Es fundamental que el alumno conozca en todo momento las normas indicadas en dicho
documento. Por tanto en este apartado se explicará únicamente el contenido que se espera en
la entrega.
35
3.2.1 Fechas y forma de entrega
Las fechas límite para las diferentes entregas son las siguientes:
Para entregar su práctica el alumno debe acceder a la sección entrega de trabajos del Curso
Virtual. Si una vez entregada desea corregir algo y entregar una nueva versión, puede hacerlo
las veces que sea necesario hasta la fecha límite. Los profesores no tendrán acceso a los
trabajos hasta dicha fecha, y por tanto no realizarán correcciones o evaluaciones de la práctica
antes de tener todos los trabajos. En ningún caso se enviarán las prácticas por correo
electrónico a los profesores.
Puesto que la compilación y ejecución de las prácticas de los alumnos se realiza de forma
automatizada, el alumno debe respetar las normas de entrega indicadas en el enunciado de la
práctica.
Se recuerda que es necesario superar una sesión de control obligatoria a lo largo del curso
para aprobar la práctica y la asignatura. En la sesión presencial obligatoria el tutor comprobará
que el alumno ha realizado:
- Análisis semántico
o comprobación de tipos
- Código intermedio
36
Dicho archivo contendrá la estructura de directorios que se proporcionará en las directrices de
implementación. Esta estructura debe estar en la raíz del fichero zip. No se debe de incluir
dentro de otro directorio, del tipo, por ejemplo: “pdl”, “practica”, “arquitectura”, etc.
En cuanto a la memoria, será un breve documento llamado "memoria" con extensión .doc o
.pdf y situado en el directorio correspondiente de la estructura dada.
Portada obligatoria
4. Indicaciones especiales
En este punto se han de incluir aquellas apreciaciones que el alumno quiera hacer
sobre su práctica. Por ejemplo: partes incompletas, interpretaciones dudosas del
enunciado y soluciones tomadas, apuntes sobre necesidades de la ejecución del
compilador, etc.
5. Conclusiones
6. Gramática
En cada apartado habrá que incluir únicamente comentarios relevantes sobre cada parte y no
texto “de relleno” ni descripciones teóricas, de forma que la extensión de la memoria esté
comprendida aproximadamente entre 2 y 5 hojas (sin incluir el esquema de las producciones
de la gramática). En caso de que la memoria no concuerde con las decisiones tomadas en la
implementación de cada alumno la práctica puede ser considerada suspensa.
37
Es importante tener claro que la entrega debe de contener todo el proceso de compilación.
Es decir, han de incluirse las siguientes fases: análisis léxico, análisis sintáctico, análisis
semántico, generación de código intermedio y código final. Para las dos primeras etapas
(análisis léxico y sintáctico) se puede usar la implementación proporcionada, o se puede
realizar una implementación de las mismas.
Otra estructura importante es la tabla de tipos (TT), cuya responsabilidad es mantener una
definición computacional de todos los tipos (primitivos y compuestos) que están accesibles por
el programador dentro de un programa fuente. Las entradas de la TS mantienen referencias a
esta tabla para tipificar sus elementos (variables, constantes funciones y procedimientos).
38
resultados de cómputo intermedio. EN NINGUN CASO estos operandos son direcciones físicas
de memoria. El trabajo de esta fase consiste básicamente en insertar en las acciones
semánticas de Cup las instrucciones java pertinentes para realizar la traducción de un AST a
una secuencia de CUADRUPLAS. Al final de esta etapa ya no necesitaremos más herramientas,
ni tampoco el programa fuente: tendremos una TS llena y una lista de cuádruplas que
describen todo el contenido del programa fuente.
Una vez obtenido el código final, éste puede probarse utilizando la herramienta ENS2001 que
permite interpretar el código generado. Así podremos ejecutar un programa compilado con
nuestro compilador y probar su funcionamiento.
3.2.3.5 Dificultades
Es muy importante dedicar tiempo a entender el proceso completo de compilación y
ejecución, especialmente a la hora de distinguir dos conceptos fundamentales: tiempo de
compilación y tiempo de ejecución. La diferencia es que en tiempo de compilación tenemos a
nuestra disposición la tabla de símbolos, que nos dice, por ejemplo, en qué dirección de
memoria está una determinada variable. Sin embargo, al ejecutar el programa ya no tenemos
ningún apoyo más que el propio código generado, de forma que el código debe contener toda
la información necesaria para que el programa funcione correctamente. Esto resulta
especialmente complicado a la hora de manejar llamadas a funciones, especialmente si son
llamadas recursivas. Para mantener esta información en tiempo de ejecución se reserva un
espacio en memoria llamado registro de activación, que almacena elementos como la
dirección de retorno, el valor devuelto, los parámetros y las variables locales.
El registro de activación y la gestión de memoria son probablemente los puntos más delicados
y difíciles para el alumno, por lo que recomendamos abordarlos con cuidado y apoyarse en la
teoría.
39
4 Herramientas
Para el desarrollo del compilador se utilizan herramientas de apoyo que simplifican
enormemente el trabajo. En concreto se utilizarán las indicadas en los siguientes apartados.
Para cada una de ellas se incluye su página web e información relacionada. En el curso virtual
de la asignatura pueden encontrarse una versión de todas estas herramientas junto con
manuales y ejemplos básicos.
4.1 JFlex
Se usa para especificar analizadores léxicos. Para ello se utilizan reglas que definen expresiones
regulares como patrones en que encajar los caracteres que se van leyendo del archivo fuente,
obteniendo tokens.
4.2 Cup
Esta herramienta permite especificar gramáticas formales facilitando el análisis sintáctico para
obtener un analizador ascendente de tipo LALR. Además Cup también permite asociar acciones
java entre los elementos de la parte derecha de la gramática de forma que éstas se vayan
ejecutando a medida que se va construyendo el árbol de análisis sintáctico. Estas acciones
permitirán implementar las fases de análisis semántico y generación de código intermedio.
4.4 Ant
Ant es una herramienta muy útil para automatizar la compilación y ejecución de programas
escritos en java. Ant permite evitar situaciones habituales en las que, debido a configuraciones
particulares del proceso de compilación, la práctica sólo funciona en el ordenador del alumno.
40
5 Ayuda e información de contacto
Es fundamental y obligado que el alumno consulte regularmente el Tablón de Anuncios y los
foros de la asignatura, accesible desde el Curso Virtual para los alumnos matriculados. En caso
de producirse errores en el enunciado o cambios en las fechas siempre se avisará a través de
este medio. El alumno es, por tanto, responsable de mantenerse informado.
También debe estudiarse bien el documento que contiene las normas y el calendario de la
asignatura, incluido en el Curso Virtual.
Se recomienda también la utilización de los foros como medio de comunicación entre alumnos
y de estos con el Equipo Docente. Se habilitarán diferentes foros para cada parte de la
práctica. Se ruega elegir cuidadosamente a qué foro dirigir el mensaje. Esto facilitará que la
respuesta bien por otros compañeros, bien por el Equipo Docente, sea más eficiente.
Esto no significa que la práctica pueda hacerse en común, por tanto no debe compartirse
código. Se utilizará un programa de detección de copias al corregir la práctica. La detección de
prácticas copiadas supondrá el SUSPENSO en todo el curso (junio y septiembre) para todos los
implicados.
El alumno puede plantear sus dudas al tutor del Centro o a los profesores
([email protected]). El profesor encargado de la práctica es Alvaro Rodrigo
([email protected]). El alumno debe comprobar si su duda está resuelta en la sección de
Preguntas Frecuentes (FAQ) o en los foros de la asignatura antes de contactar con el tutor o los
profesores. Por otra parte, si el alumno tiene problemas relativos a su tutor o a su Centro
Asociado, debe contactar con el coordinador de la asignatura Anselmo Peñas
([email protected]).
41