El propósito de esta página es proporcionar un resumen de la sintaxis, expresada en BNF (Formas de Backus-Naur), del subconjunto del lenguaje Pascal contemplado en la asignatura Programación II, curso 00/01.
Debe quedar claro que estas definiciones tan sólo incorporan la sintaxis básica, y por lo tanto el que un elemento cumpla una definición no significa necesariamente que sea una construcción correcta en Pascal. Por ejemplo, la definición de sentencia de asignación, <Variable> := <Expresion> no incorpora la información de que el resultado de la evaluación de la expresión debe ser de un tipo de datos compatible con el tipo de la variable.
Ese tipo de información corresponde a la semántica del lenguaje, y sólo se incorpora de manera parcial mediante construcciones del tipo Identificador de funcion, que restringen los elementos permitidos en una parte de la definición.
Cuando aparezca el símbolo & en la parte derecha de una definición, significa que existe un enlace a una página donde se proporciona información adicional sobre ese elemento (significado, restricciones, etc).
Para facilitar la búsqueda de una definición concreta, elija el término en la siguiente lista y pulse "Ver Definicion", o comienze con un elemento de alto nivel como Programa , Unidad o Tipo de dato.
Los cambios con respecto a Programación I se refieren al Tipo puntero (punteros genéricos), Tipo registro (registros con parte variante) y a los Tipos procedurales.
Este tipo de estructura tiene las siguientes características:
Este tipo de estructura tiene las siguientes características:
El valor false tiene asociado un valor ordinal de 0, y el valor true un valor ordinal de 1. Al comparar valores de tipo boolean se cumple que false < true.
La notación #<Número> representa al carácter que se encuentra en esa posición de la tabla de caracteres. Es equivalente, por tanto, a chr(Número).
Comparar caracteres es comparar las posiciones que ocupan en la tabla de caracteres del sistema. No existe (todavía) una tabla de caracteres universal, por lo que sólo podemos suponer las siguientes características, comunes a la mayoria de tablas existentes:
El literal nil es compatible con todos los tipos puntero. Como la dirección representada por nil no puede corresponder a la de ninguna variable dinámica, asignar a un puntero este valor sirve para indicar que el puntero no almacena una dirección válida.
Los tipos enteros sirven para representar valores numéricos sin parte decimal. Tienen una limitación de rango, es decir sólo pueden representar valores dentro de unos límites determinados. Si alguna operación aritmetica produce un valor fuera de esos limites, se produce un error de desbordamiento. Los límites, para la versión de Turbo-Pascal que utilizamos, son:
Maxint es una constante predefinida que indica el valor máximo que se puede representar en un valor de tipo integer.
Nota: Maxint = 215-1 = 32.767, 231 = 2.147.483.648. Los valores se codifican internamente en complemento a dos.
Los operadores aritmeticos enteros operan sobre valores de tipo integer y longint. Si un operando es de tipo integer y el otro de tipo longint, el operando de tipo integer se convierte automáticamente a un valor de tipo longint y se pasa a calcular el resultado, que será de tipo longint.
En el caso de los operadores cociente (div) y resto (mod), se cumplen las siguientes relaciones: Dados c := a div b, r := a mod b, se tiene que:
Las ecuaciones (3) y (4) indican que cuando el dividendo y/o el divisor son negativos, los valores absolutos del cociente y el resto proporcionados son iguales a los obtenidos cuando el dividendo y el divisor son positivos, y los signos del cociente y el resto se eligen de manera que se cumpla la ecuación (1).
Los tipos reales sirven para representar valores numéricos con parte decimal. La representación de valores tiene las siguientes limitaciones:
Esta última limitación indica que, salvo casos especiales, los valores de tipo real no son una representación exacta del valor real, sino tan solo una aproximación. Por lo tanto, es necesario evitar en lo posible comparaciones de igualdad y desigualdad con valores de tipo real, ya que pueden dar lugar a resultados inesperados. Por ejemplo, la comparación 3*(1/3) = 1.0 da como resultado false.
Los operadores suma, resta y multiplicación proporcionan un resultado de tipo real cuando alguno de sus operandos es de ese tipo. Si un operando es de tipo integer o longint, se convierte automáticamente a un valor de tipo real y se pasa a calcular el resultado.
El operador de división (/), sin embargo, proporciona siempre un resultado de tipo real, incluso cuando ambos operandos son enteros y la división tiene un resultado exacto.
En la siguiente tabla se muestran los operadores de conjuntos, el tipo de sus argumentos y de su resultado:
Operador | Significado | Operando 1 | Operando 2 | Resultado |
+ | Unión | Conjunto tipo base T1 | Conjunto tipo base T2 | Conjunto tipo base T3 |
* | Intersección | Conjunto tipo base T1 | Conjunto tipo base T2 | Conjunto tipo base T3 |
- | Diferencia | Conjunto tipo base T1 | Conjunto tipo base T2 | Conjunto tipo base T3 |
= | Igualdad | Conjunto tipo base T1 | Conjunto tipo base T2 | Boolean |
<> | Desigualdad | Conjunto tipo base T1 | Conjunto tipo base T2 | Boolean |
<= | Subconjunto de | Conjunto tipo base T1 | Conjunto tipo base T2 | Boolean |
in | Pertenencia | Valor de tipo T1 | Conjunto tipo base T2 | Boolean |
Los tipos de datos T1 y T2 deben cumplir alguna de las siguientes condiciones:
El operador de concatenación de cadenas de caracteres (+) tiene la siguiente sintaxis:
Los tipos de datos T1 y T2 pueden ser cualquier tipo string (independientemente de su número máximo de caracteres) o char. El resultado es un valor de tipo string[n], donde n es el tamaño mínimo del string que puede almacenar la cadena resultante.
En Turbo-Pascal, si la cadena resultante tiene más de 255 caracteres, no se tienen en cuenta aquellos situados tras la posición 255.
La definición de un rango de valores se utiliza en la sentencia case (los límites son literales) y en el constructor de conjuntos (los limites son expresiones). Suponiendo la siguiente definición común a ambos casos:
Se deben cumplir las siguientes condiciones:
Nota: Los valores N1 y N2 pertenecen al rango resultante.
El tipo de datos registro representa a un conjunto de elementos cuyos tipos de datos pueden ser distintos (en contraste con los arrays, cuyos elementos pertenecen al mismo tipo). Cada elemento se denomina campo, y en la declaración de un tipo registro se proporciona a cada campo un nombre distinto y se indica el tipo de datos que almacena.
Por ejemplo, supongamos que deseamos representar mediante el siguiente tipo registro la información asociada a cada empleado de una empresa, los cuales pueden ser fijos, eventuales o autónomos:
type TFecha = record Anno : integer; Mes : 1..12; Dia : 1..31 end; TContrato = (cFijo, cEventual, cAutonomo); TEmpleado = record DNI : string[10]; Nombre, Apellido1, Apellido2 : string[40]; Contrato : TContrato; Inicio : TFecha; { Sólo si es fijo o eventual } Final : TFecha; { Sólo si es eventual } Salario : real; { Sólo si es fijo o eventual } Tarifa : real; { Sólo si es autonomo } end;
Un empleado autónomo no tiene fecha de inicio ni finalización de contrato ya que no percibe un salario, sino que se le paga por trabajos realizados de acuerdo a una tarifa horaria. Un empleado fijo no tiene fecha de finalización de contrato. Por último, un empleado fijo o eventual cobra un salario anual, y un empleado autónomo una tarifa horaria.
Definiendo el registro de esa forma se está desperdiciando espacio en memoria, ya que si el valor del campo contrato es cFijo no se utilizarán los campos Final ni Tarifa. Si el valor es cEventual no se utilizará el campo Tarifa, y si el valor es cAutonomo no se utilizarán los campos Inicio, Final y Salario.
Con el único propósito de gestionar de una manera más eficiente la memoria en este tipo de situaciones, en las que dependiendo del valor de un campo se necesita almacenar información distinta y excluyente entre sí, Pascal permite que los registros incluyan una (y sólo una) parte variante al final de las declaraciones de los campos comunes.
La sintaxis de esta parte variante es parecida a la definición de una alternativa múltiple: Entre las palabras case y of se incluye la declaración de un campo de tipo ordinal (que se denomina campo selector). El valor de este campo seleccionará cual de los siguientes grupos de campos tienen sentido para cada posible valor del campo selector. A continuación, utilizando una sintaxis similar a la de la estructura case, se escriben las declaraciones de los campos correspondientes a cada valor del campo selector agrupandolos entre paréntesis. En el ejemplo anterior, la definición usando parte variante del registro TEmpleado sería:
TEmpleado = record DNI : string[1..10]; Nombre, Apellido1, Apellido2 : string[40]; case Contrato : TContrato of cFijo : (InicioFijo : TFecha; SalarioFijo : real); cEventual : (InicioEventual,FinalEventual : TFecha; SalarioEventual : real); cAutonomo : (Tarifa: real) end;
Se debe tener cuidado con las siguientes carácteristicas de la parte variante:
En un registro de tipo TEmpleado podemos acceder a los campos DNI, Nombre, Apellido1, Apellido2, Contrato, InicioFijo, SalarioFijo, InicioEventual, FinalEventual, SalarioEventual y Tarifa. Sin embargo, los grupos de campos (InicioFijo, SalarioFijo), (InicioEventual, FinalEventual, SalarioEventual) y (Tarifa) comparten la misma zona de la memoria.
La ejecución de una sentencia de asignación comprende los siguientes pasos:
Para ampliar información, vease Tipos de datos compatibles.
Todos los tipos de datos son compatibles consigo mismos. Dos tipos distintos son compatibles entre sí cuando cumplen alguna de las siguientes condiciones:
El orden de evaluación de los elementos de una expresión sigue las siguientes normas:
Para que una expresión pueda evaluarse se deben dar las siguientes condiciones:
Nota: En el caso de parámetros por variable, el tipo de datos de la variable parámetro actual y el tipo de datos de la variable parámetro formal debe ser exactamente el mismo.
Los niveles de precedencia de los operadores de Pascal son los siguientes:
Nivel | Tipo de operador | Operadores |
4 | Operadores unarios | not, - |
3 | Operadores multiplicativos | *, /, div, mod, and |
2 | Operadores aditivos | +, -, or |
1 | Operadores relacionales | =, <>, <, >, <=, >=, in |
El tipo de datos conjunto tiene las siguientes características especiales:
El tipo de datos cadena de caracteres (string) es una especialización basada en los arrays unidimensionales de caracteres a la que se añade funcionalidad para facilitar la representación de texto. Entre sus características especiales destacan:
Los ficheros, al representar datos externos al programa y tener un acceso secuencial, tienen un tratamiento distinto al de los otros tipos estructurados. Algunas de esas características especiales son:
Definición de ficheros:La tabla siguiente muestra características de los tipos de datos predefinidos de Turbo-Pascal:
Tipo de dato | Simple | Ordinal | Elemental | Imprimible | Literales | Acceso |
Entero | Si | Si | Si | Si | Si | - |
Real | Si | No | Si | Si | Si | - |
Carácter | Si | Si | Si | Si | Si | - |
Lógico | Si | Si | Si | No | Si | - |
Enumerado | Si | Si | Si | No | Si | - |
Subrango | Si | Si | Si | (1) | Si | - |
Puntero | Si | No | Si | No | No | - |
Array | No | No | No | No | No | Por índice |
Registro | No | No | No | No | No | Por campo |
Fichero | No | No | No | No | No | Secuencial |
Conjunto | No | No | No | No | Si | (2) |
String | No | No | Si | Si | Si | Por índice |
A la hora de trabajar con variables dinámicas es necesario asegurarse de que la dirección almacenada en la variable puntero corresponde realmente a la de una variable dinámica, puesto que Pascal no verifica que sea así: Si usamos una dirección incorrecta, podemos inadvertidamente estar cambiando el valor de otras variables dinámicas, estáticas o incluso el propio código de nuestro programa o el de cualquier otro que se esté ejecutando en el ordenador.
Para que esto no ocurra, es necesario asegurarse de que al puntero usado para acceder a la variable dinámica le ha sido asignado una dirección válida mediante una llamada al procedimiento new o por una asignación de un puntero que almacenaba una dirección válida.
Tras la llamada al procedimiento dispose, la dirección que almacena el puntero proporcionado deja de ser válida (la memoria que ocupaba la variable dinámica situada en esa dirección se libera y pasa a estar disponible para otros usos).
La definición de un tipo puntero tiene dos partes diferenciadas:
Las variables de tipo puntero almacenan direcciones de memoria. No es necesario que conozcamos qué es exactamente una dirección de memoria, sino tan solo que mediante esos valores es posible hacer referencia a variables dinámicas.
La dirección almacenada en un puntero puede ser, en un momento dado, válida o no válida. Es válida cuando represente la dirección de una variable dinámica existente.
Una variable dinámica existe cuando ha sido creada mediante una llamada al procedimiento new, y todavia no ha sido destruida mediante una llamada al procedimiento dispose.
Es necesario tener en cuenta que los valores de tipo puntero son un mero intermediario para permitir el trabajar con variables dinámicas, y que no guardan más relación con ellas que las de almacenar su dirección.
Una variable declarada con el tipo de datos pointer se denomina puntero genérico, y almacenará valores de tipo puntero (direcciones de memoria) pero, a diferencia de los punteros normales (variables definidas con la sintaxis ^tipo asociado, ver apartado anterior), se desconoce cual es el tipo de datos de las variables dinamicas cuya dirección se almacena en la variable puntero genérico.
Eso significa que no es posible utilizar directamente punteros genéricos para crear, destruir o acceder a variables dinámicas. La única acción permitida es asignar a un puntero genérico el valor de cualquier otro puntero (ya sea genérico o no).
La única utilidad de los punteros genéricos es la de poder almacenar direcciones de variables dinámicas sin necesidad de preocuparse de su tipo de datos. Por supuesto, a la hora de acceder a la variable dinámica cuya dirección se almacena en el puntero genérico es necesario recordar cual es su tipo de datos e indicárlo en el programa mediante una conversión de tipo, que tiene la siguiente sintáxis:
La construcción anterior permite convertir un puntero genérico en un puntero normal, proporcionando el tipo de datos asociado a las variables dinámicas cuya dirección se guarda en el valor del puntero genérico.
Los punteros genéricos se usan, principalmente, para poder crear datos estructurados que puedan almacenar valores de cualquier tipo, sin necesidad de indicarlo explicitamente al definirlas. En Pascal, la única manera de hacerlo es creando variables dinámicas y almacenando su dirección en variables de tipo puntero genérico. Para acceder a los valores almacenados en esas variables dinámicas es necesario proporcionar el tipo de datos al que pertenece cada variable dinámica, lo que se hace mediante la conversión de tipos anterior.
En el ejemplo siguiente definimos un array donde cada posición almacena un puntero genérico que, en este caso, apunta a una variable dinámica de un tipo distinto:
type TCadena = String[100]; PInteger = ^Integer; { Puntero a variables dinamicas de tipo entero } PReal = ^Real; { Puntero a variables dinamicas de tipo real } PCadena = ^TCadena; { Puntero a variables dinamicas de tipo cadena } var P1 : PInteger; P2 : PReal; P3 : PCadena; Vec : array[1..3] of pointer; begin { Creamos tres variables dinámicas de tipos distintos } new(P1); P1^ := 10; new(P2); P2^ := 3.141592; new(P3); P3^ := 'Pascal'; { Asignamos las direcciones de esas variables al vector } Vec[1] := P1; Vec[2] := P2; Vec[3] := P3; { El vector contiene direcciones de tres variables dinámicas cuyos tipos difieren. Para acceder a esas variables es necesario realizar la conversión de tipos adecuada a cada una: } write(output,'Vector = ',PInteger(Vec[1])^,', ',PReal(Vec[2])^,', ',PCadena(Vec[3])^); dispose(PInteger(Vec[1])); dispose(PReal(Vec[2])); dispose(PCadena(Vec[3])) end.
Nota: Trabajar con punteros genéricos es peligroso, ya que es imposible para el compilador comprobar que estemos utilizando una conversión de tipos adecuada. En el ejemplo anterior, nada nos impediría hacer la siguiente asignación justo antes de la sentencia write:
PCadena(Vec[1])^ := 'Esto no se debe hacer';
En ese caso, estariamos tratando como una cadena de caracteres a una variable que en realidad es de tipo integer, lo que puede dar lugar a errores impredecibles y muy dificiles de depurar (en el ejemplo anterior es posible que estemos accediendo y cambiando el valor de zonas de la memoria ocupadas por otras variables, código, o incluso otros programas distintos a éste).
Se recomienda, por lo tanto, extremar las precauciones y utilizar punteros genéricos sólo en aquellos casos en que no exista otra alternativa (al definir tipos abstractos de datos, por ejemplo).
Los tipos de datos procedurales permiten tratar procedimientos y funciones como valores que pueden ser asignados a variables del tipo procedural adecuado.
La sintaxis de la definición de un tipo procedural es igual a la declaración de la cabecera de una función o procedimiento, excepto que no debe aparecer el identificador del nombre de la función o del procedimiento. A esta definición se la suele denominar prototipo de función o procedimiento. A continuación se muestran tres ejemplos:
type TFunc1 = function(A,B: real): boolean; TProc1 = procedure(A: char; var N: integer); TProc2 = procedure;
Las variables de tipo TFunc1 almacenarán referencias a funciones que se ajusten al prototipo dado en la definición, es decir, que tengan exactamente dos parámetros de entrada de tipo real y proporcionen un resultado de tipo boolean. Las variables de tipo TProc1 almacenarán referencias a procedimientos cuyo primer parámetro valor de tipo char y el segundo una variable de tipo integer. Por último, las variables de tipo TProc2 almacenarán referencias a procedimientos que no reciban ningún parámetro.
Con variables de tipo procedural sólo es posible realizar dos tipos de acciones:
A continuación se muestra un ejemplo de asignaciones y llamadas utilizando tipos procedurales:
{$F+} program ejemplo(input,output); type TFunc = function(A: integer): boolean; function impar(A: integer): boolean; begin impar := A mod 2 = 1 end; function primo(A: integer): boolean; var I: integer; begin if A < 4 then primo := true else begin I := 2; while A mod I <> 0 do I := I + 1; Primo := A = I end end; function nulo(A: real): boolean; begin nulo := A = 0 end; var F : TFunc; { variable de tipo procedural } N : integer; begin readln(input,N); F := impar; if F(N) then writeln(output,N,' es impar') else writeln(output,N,' no es impar'); F := primo; if F(N) then writeln(output,N,' es primo') else writeln(output,N,' no es primo'); { Esta asignación da error ya que la función nulo no se ajusta al prototipo definido por TFunc } F := nulo end.
Turbo-Pascal permite que escribamos las zonas de declaraciones en cualquier orden, e incluso repitamos zonas en sitios distintos. Para los programas desarrollados en el marco de la asignatura se va a imponer, sin embargo, la siguiente restricción: Sólo debe existir una zona de declaración de variables y debe estar situada despues de cualquier declaración de subprogramas.
De esta forma se garantiza que ningún subprograma puede hacer uso de variables globales a él, cumpliendo la regla de la programación modular que establece que los módulos deben ser independientes (respecto a los datos que manejan) entre sí.
Operaciones matemáticas:
Subprograma | Tipo | Parámetros | Resultado |
abs(x) | Función | x de tipo entero o real | El valor absoluto de x |
sqr(x) | Función | x de tipo entero o real | El cuadrado de x (1) |
sqrt(x) | Función | x de tipo entero o real | La raiz cuadrada de x (2) |
odd(x) | Función | x de tipo entero | true si x es impar, false si es par |
random(x) | Función | x de tipo entero | Un entero entre 0 y x - 1, escogido al azar |
sin(x) | Función | x de tipo real | El seno de x (x son radianes) |
cos(x) | Función | x de tipo real | El coseno de x (x son radianes) |
arctan(x) | Función | x de tipo real | El arcotangente de x, expresado en radianes |
ln(x) | Función | x de tipo real | El logaritmo neperiano de x |
exp(x) | Función | x de tipo real | La exponencial de x (ex) |
Operaciones de conversión entre tipos:
Subprograma | Tipo | Parámetros | Resultado |
chr(x) | Función | x de tipo entero | El carácter en la posición x de la tabla de caracteres |
trunc(x) | Función | x de tipo real | La parte entera de x |
round(x) | Función | x de tipo real | El valor entero más cercano a x |
Operaciones ordinales:
Subprograma | Tipo | Parámetros | Resultado |
ord(x) | Función | x de tipo ordinal | La posición del valor x en su tipo de datos (3) |
succ(x) | Función | x de tipo ordinal | El valor siguiente de su mismo tipo |
pred(x) | Función | x de tipo ordinal | El valor anterior de su mismo tipo |
(1) El resultado pertenece al mismo tipo de datos que el argumento. Se debe tener cuidado al utilizar enteros, ya que el resultado debe ser menor que maxint (en la versión 6.0 de Turbo-Pascal eso significa menor que 32768), independientemente de que la variable a la que asignemos el resultado sea de tipo longint o real. Como ejemplo, pruebe a ejecutar el siguiente programa:
var x: real; begin x := sqr(1000); write(output,'10^2=',sqr(10),' 1000^2=',x:1:0,' 1000^2=',sqr(1000.0):1:0) end.
(2) El resultado es siempre de tipo real, independientemente de que el argumento sea un cuadrado perfecto o no.
(3) En el caso de argumentos enteros, ord devuelve su mismo valor. En el resto de casos, devuelve la posición del valor comenzando a contar por cero. Nota: La función ord no tiene en cuenta si el argumento proviene o no de una variable de tipo subrango.
Subprograma | Tipo | Parámetros | Resultado |
length(c) | Función | c valor de tipo string | Longitud de la cadena |
copy(c,i,n) | Función | c valor de tipo string i,n de tipo entero |
La subcadena de c que comienza a partir de la posición i y tiene longitud n |
insert(s,c,i) | Procedimiento | s valor de tipo string c variable de tipo string i de tipo entero |
Inserta la subcadena s en la cadena c a partir de la posición i |
delete(c,i,n) | Procedimiento | c variable de tipo string i,n de tipo entero |
Elimina de c la subcadena que comienza a partir de la posición i y tiene longitud n |
pos(s,c) | Función | s, c valor de tipo string | La primera posición de c donde aparece la subcadena s, o cero si no aparece |
Subprograma | Tipo | Parámetros | Resultado |
assign(f,c) | Procedimiento | f de tipo fichero c de tipo string |
Asigna un fichero físico a un fichero lógico |
reset(f) | Procedimiento | f de tipo fichero | Abre un fichero para lectura |
rewrite(f) | Procedimiento | f de tipo fichero | Abre un fichero para creación-escritura |
eof(f) | Función | f de tipo fichero de datos | false si quedan datos por leer true si no quedan datos por leer |
eof(f) | Función | f de tipo fichero de texto | false si quedan lineas true si no quedan lineas |
eoln(f) | Función | f de tipo fichero de texto | false si quedan caracteres en la linea actual true si no quedan caracteres en la linea actual |
read(f,v) | Procedimiento | f de tipo fichero de datos v variable de tipo base |
Lee un dato del fichero y lo asigna a la variable |
read(f,v) | Procedimiento | f de tipo fichero de texto v variable de tipo imprimible |
Lee caracteres, los traduce a un valor del tipo de la variable y se lo asigna |
readln(f,v) | Procedimiento | f de tipo fichero de texto v variable de tipo imprimible |
Igual que read, y además pasa a la siguiente linea ignorando los caracteres que quedan en la linea actual |
readln(f) | Procedimiento | f de tipo fichero de texto | Pasa a la siguiente linea ignorando los caracteres que quedan en la linea actual |
write(f,v) | Procedimiento | f de tipo fichero de datos v variable de tipo base |
Escribe en el fichero el dato almacenado en la variable |
write(f,e) | Procedimiento | f de tipo fichero de texto e expresión de tipo imprimible |
Traduce el valor resultante de la expresión a una secuencia de caracteres y la escribe en el fichero |
writeln(f,e) | Procedimiento | f de tipo fichero de texto e expresión de tipo imprimible |
Igual que write, y además escribe los caracteres que indican el final de linea |
writeln(f) | Procedimiento | f de tipo fichero de texto | Escribe los caracteres que indican el final de linea |
close(f) | Procedimiento | f de tipo fichero | Cierra un fichero |