Oberon-2 es un lenguaje de propósito general. Es de la familia de los Pascales (de hecho, uno de sus creadores es el padre de Pascal: Niklaus Wirth). Evolución del lenguaje Oberon, sus características más importantes son: estructura en bloques, modularidad, compilación separada, tipaje estático con fuerte chequeo de tipo, extensión de tipos y procedimientos de tipo.
La extensión de tipo hace de Oberon-2 un lenguaje orientado a
objeto. Un objeto es una variable de un tipo de dato abstracto, consistente
en datos privados (su “estado”) y procedimientos para operar con esos datos.
Los tipos de datos abstractos se declaran como registros extensibles. Oberon-2
cubre muchos de los términos de los lenguajes orientados a objeto
con el vocabulario establecido en los lenguajes imperativos, en orden a
minimizar el número de nociones para similares conceptos.
Vocabulario y representación
La representación de símbolos, en términos de caracteres, se definen usando el juego ASCII. Son símbolos los identificadores, números, cadenas de caracteres, operadores y delimitadores. Se deben observar las siguientes reglas léxicas: los espacios en blanco y las rupturas de línea no deben ocurrir dentro de los símbolos (excepto en comentarios, o los espacios en blanco dentro de cadenas de caracteres). Estos se ignorarán salvo que sean esenciales para separar dos símbolos consecutivos. Las letras mayúsculas y minúsculas se consideran distintas.
Identificador = letra {letra | dígito}
2. Números son constantes enteros o reales (con o sin signo). El tipo de una constante entera es el tipo mínimo que pueda almacenar su valor. Si se especifica la constante con el sufijo H la representación será hexadecimal; en caso contrario será en decimal.
Un número real siempre contiene un punto decimal. Opcionalmente puede contener un factor de escala decimal. La letra E (o D) indica exponenciación (las veces que se eleva 10 a la potencia referida). Un número real es de tipo REAL, salvo que su factor de escala contenga la letra D, en cuyo caso será de tipo LONGREAL.
Ejemplos:
Número Tipo Valor
1991 INTEGER 1991
0DH SHORTINT 13
12.3 REAL 12.3
4.567E8 REAL 456700000
0.57712566D-6 LONGREAL
0.00000057712566
3. Constante de carácter. Denotado por el número ordinal del carácter en notación hexadecimal seguido de la letra X.
Ejemplos:
El carácter “A” = 65 en decimal = 41X en hexadecimal
VAR caracter;
BEGIN
caracter := 41X;
4. Cadena de caracteres. Es una secuencia de caracteres encerrados entre comillas (simples o dobles). El tipo de comilla de apertura y el de cierre ha de ser el mismo, y no debe aparecer dentro de la cadena. El número de caracteres en una cadena es llamado su longitud. Una cadena de caracteres de longitud 1 puede usarse en cualquier lugar donde pueda usarse un carácter constante, y viceversa.
Ejemplos:
“Oberon-2” “Don’t worry!” “x”
Obsérvese el segundo ejemplo, donde se incluye una comilla simple
dentro de una cadena delimitada por comillas dobles. Del mismo modo, podría
crearse una cadena de caracteres que contenga comillas dobles si se delimita
dicha cadena con comillas simples: ‘”Buzz”
Aldrin’
5. Operadores y delimitadores son caracteres especiales, pares de caracteres, o palabras reservadas que se listan más abajo. Las palabras reservadas consisten exclusivamente en letras mayúsculas, y no pueden ser usadas como identificadores.
+ := ARRAY IMPORT RETURN
- ^ BEGIN IN THEN
* = BY IS TO
/ # CASE LOOP TYPE
~ < CONST MOD UNTIL
& > DIV MODULE VAR
. <= DO NIL WHILE
, >= ELSE OF WITH
; .. ELSIF OR
| : END POINTER
( ) EXIT PROCEDURE
[ ] FOR RECORD
{ }
IF REPEAT
6. Comentarios. Se pueden insertar entre cualesquiera de dos
símbolos en un programa. Estos son secuencias de caracteres arbitrarios,
encerrados entre (*
y *), y que no afectan
al funcionamiento del programa. Es posible anidar comentarios.
Cada identificador que aparezca en un programa debe ser introducido en una declaración, salvo si es un identificador predeclarado. Las declaraciones también especifican ciertas propiedades permanentes de un objeto, sea una constante, un tipo, una variable o un procedimiento. El identificador es entonces usado para referirse al objeto asociado.
El ámbito de un objeto x se extiende desde el punto en que se declara hasta el final del bloque (módulo, procedimiento o registro) en el que se ubica dicha declaración, por lo que el objeto es local. Esto excluye los ámbitos de los objetos nombrados igual que se declaren en bloques anidados. Las reglas de ámbito son:
2. Un objeto solo puede ser referenciado dentro de su ámbito.
3. Un tipo T de la forma POINTER TO T1 puede ser declarado antes del ámbito de T1. La declaración de T1 debe estar a continuación dentro del mismo bloque en que T es local.
4. Los identificadores que denotan los campos de un registro o procedimientos de tipo son válidos únicamente dentro de los designadores de registro.
Los siguientes identificadores están predeclarados:
ABS ASH BOOLEAN
CAP CHAR CHR
COPY DEC ENTIER
EXCL FALSE HALT INC
INCL INTEGER LEN
LONG LONGINT LONGREAL
MAX MIN NEW ODD
ORD REAL SET
SHORT SHORTINT SIZE
TRUE
Declaración de constantes
Una declaración de constante asocia un identificador con un valor constante. Una expresión constante es aquella que puede ser evaluada mediante un análisis textual sin necesidad de ejecutar el programa. Sus operandos pueden ser constantes o funciones predeclaradas que puedan ser evaluadas en tiempo de compilación. Ejemplos de declaraciones constantes son:
N = 100
limite = 2 * N – 1
conjuntoTotal = {MIN(SET)..MAX(SET)}
Declaraciones de tipo
Un tipo de dato determina el conjunto de valores que una variable de dicho tipo puede asumir, y las operaciones que son aplicables. Una declaración de tipo asocia un identificador con un tipo. En el caso de tipos estructurados (registros y matrices) también se define la estructura de las variables de estos tipos.
Ejemplos:
Tabla = ARRAY N OF REAL
Arbol = POINTER TO Nodo
Nodo = RECORD
clave: INTEGER;
derecha,
izquierda: Arbol
END
ArbolCentrado = POINTER TO NodoCentrado
NodoCentrado = RECORD (Nodo)
ancho: INTEGER;
subnodo:
Arbol
END
Funcion = PROCEDURE (x: INTEGER):
INTEGER
Tipos básicos
Los tipos básicos se denotan mediante identificadores predeclarados. Los valores de los tipos básicos son los siguientes:
LONGREAL Ê REAL Ê
LONGINT Ê INTEGER Ê
SHORTINT
Tipos matriz
Una matriz es una estructura consistente en un número de elementos que tienen todos el mismo tipo, llamado el tipo del elemento. El número de elementos de una matriz es llamada su longitud. Los elementos de una matriz se designan por índices, que son enteros entre 0 y su longitud menos 1.
Un tipo de la forma
ARRAY L0, L1, …, Ln OF T
se interpreta como una abreviación de
ARRAY L0 OF
ARRAY L1
OF
…
ARRAY Ln OF T
Las matrices declaradas sin longitud se llaman matrices abiertas. Se restringen a los tipos base de puntero, tipos de elemento de tipos de matriz abierta, y tipos de parámetros formales. Ejemplos:
ARRAY 10, N OF INTEGER
ARRAY OF CHAR
Tipos registro
Un tipo registro es una estructura consistente en un número fijo de elementos, llamados campos, con diversos tipos posibles. La declaración de un tipo registro especifica el nombre y tipo de cada campo. El ámbito de los identificadores de campo se extiende desde el punto de su declaración hasta el final del tipo registro, pero también puede ser visible dentro de los designadores referentes a los elementos de las variables de registro. Si un tipo registro se exporta, los identificadores de los campos que han de ser visibles fuera del módulo donde se declaran deben ser también marcados para exportación: son llamados campos públicos; los elementos no marcados se llaman campos privados.
Los tipos registro son extensibles, es decir, un tipo registro puede ser declarado como una extensión de otro tipo registro, como se indica en el ejemplo:
T0 = RECORD x: INTEGER END
T1 = RECORD (T0) y: REAL END
T1 es una extensión directa de T0, y T0 es el tipo base directo de T1. Un tipo extendido T1 consta de los campos de su tipo base y de los campos que se declaran en T1. Todos los identificadores declarados en el registro extendido pueden ser diferentes de los identificadores declarados en su tipo registro base. Ejemplos de declaraciones de tipo registro son:
RECORD
dia, mes,
anio: INTEGER
END
RECORD
apellidos,
nombre: ARRAY 32 OF CHAR;
edad: INTEGER;
salario:
REAL
END
Tipos puntero
Las variables de puntero de tipo P asumen como valores punteros a variables de algún tipo T. T es llamado el tipo base de puntero de P, y debe ser del tipo registro o matriz. Los tipos puntero adoptan la extensión de la relación de sus tipos base puntero: si un tipo T1 es una extensión de T, y P1 es de tipo POINTER TO T1, entonces P1 es también una extensión de P.
Si p es una variable de tipo P = POINTER
TO T, una llamada del procedimiento predeclarado NEW(p)
reserva en el área de almacenamiento libre espacio para una variable
de tipo T. Si T es de tipo registro o matriz de longitud
fija, la asignación de área de memoria se realiza con NEW(p);
si T es de tipo matriz abierta n-dimensional, la asignación
se realiza con NEW(p,
e0, …, en-1) donde T es asignada
con el tamaño dado por la expresión e0,
…, en-1. En ambos casos un puntero a la variable reservada
es asignada a p. p es de tipo P. La variable referenciada
p^
es de tipo T. Cualquier variable puntero puede asumir el valor NIL,
que no apunta a variable alguna.
Tipos procedimiento
Las variables de un procedimiento tipo T tienen un procedimiento
(o NIL) como valor. Si un procedimiento P es asignado a una variable
de tipo T, la lista de parámetros formales de P y
T
deben coincidir. P no puede ser un procedimiento predeclarado
o de tipo, ni puede ser local a otro procedimiento.
Declaraciones de variables
Las declaraciones de variables introduce variables mediante la definición de un identificador y un tipo de dato para él.
Las variables registro y puntero tienen ambos un tipo estático (el predeclarado en su definición) y un tipo dinámico (el tipo asumido en tiempo de ejecución). Para punteros y parámetros variables del tipo registro el tipo dinámico debe ser una extensión de su tipo estático. El tipo estático determina qué campos de un registro son accesibles. El tipo dinámico es usado para invocar a los procedimientos de tipo.
Ejemplos de declaraciones de variables:
i, j, k: INTEGER
x, y: REAL
p, q: BOOLEAN
s: SET
F: Function
a: ARRAY 100 OF REAL
w: ARRAY 16 OF RECORD
nombre: ARRAY
32 OF CHAR;
cantidad:
INTEGER
END
t, c: Tree
Expresiones
Las expresiones son construcciones que denotan reglas de computación
por el cual las constantes y los valores actuales de las variables se combinan
para computar otros valores mediante la aplicación de operadores
y procedimientos de función. Las expresiones constan de operandos
y operadores. Los paréntesis pueden ser usados para expresar asociaciones
específicas de operandos y operadores.
Operandos
Con la excepción de los conjuntos de constructores y las constantes literales (números, constantes de carácter o cadenas de caracteres), los operandos se denotan mediante designadores. Un designador consta de un identificador referente a una constante, variable o procedimiento. Este identificador puede ser cualificado por un identificador de módulo y puede ser seguido por selectores si el objeto designado es un elemento de una estructura.
Si se diseña una tabla, entonces a[e] denota el elemento cuyo índice es el valor actual de la expresión e. El tipo de e debe ser un tipo entero. Un designador de la forma a[e0, e1, …, en] es equivalente a a[e0][e1]…[en]. Si r designa un registro, entonces r.f denota el campo f de r, o el procedimiento f correspondiente al tipo dinámico de r. Si p designa un puntero, p^ denota la variable que es referenciada por p. Los designadores p^.f y p^[e] puede ser abreviado como p.f y p[e], o sea, que los selectores de registro y de tabla implican dereferenciación. Si a o r son de sólo lectura, entonces también a[e] y r.f serán de sólo lectura.
Un type guard v(T) asegura que el tipo dinámico de v es T (o una extensión de T), abortando la ejecución del programa si no es así. Dentro del designador, v es entonces contemplado como teniendo el tipo estático T. Esta salvaguardia es aplicable si:
1. v es un parámetro variable de tipo registro o v es un puntero, y si
2. T es una extensión del tipo estático de v
Si el objeto designado es una constante o una variable, entonces los designadores se refieren a su valor actual. Si es un procedimiento, el designador se refiere a dicho procedimiento, salvo si es seguido por una (posiblemente vacía) lista de parámetros, en cuyo caso esto implica una activación de tal procedimiento y se mantiene para el valor resultante de su ejecución. El parámetro actual debe corresponder a los parámetros formales como en las llamadas al propio procedimiento.
Ejemplos de designadores:
i (INTEGER)
a[i] (REAL)
w[3].name[i] (CHAR)
t.left.right (Tree)
t(CenterNode).subnode (Tree)
Operadores
En las expresiones se pueden distinguir sintácticamente cuatro clases de operadores con diferentes precedencias. El operador ~ tiene la precedencia más alta, seguido de los de multiplicación y división, suma y resta, y los de relación. Los operadores de la misma precedencia se evalúan de izquierda a derecha. Por ejemplo, x - y - z es equivalente a (x - y) - z.
Los operadores disponibles están listados en las siguientes tablas. Algunos operadores son aplicables a operandos de varios tipos, denotando diferentes operaciones. En estos casos, la operación actual es identificada por el tipo de los operandos. Los operandos deben ser expresiones compatibles con el operador.
- Operadores lógicos.
OR disyunción lógica p OR q "si p entonces CIERTO, en otro caso q"
& conjunción lógica p & q "si p entonces q, en otro caso FALSO"
~ negación ~ "no p"
Estos operadores se aplican a operandos booleanos y producen un resultado booleano.
- Operadores aritméticos
+ suma
- resta
* multiplicación
/ división (cociente real)
DIV división (cociente entero)
MOD módulo o resto de la división
Los operadores +, -, * y / se aplican a operandos o tipos numéricos. El tipo del resultado es el tipo del operando que incluya el tipo del otro operando, excepto en la división /, donde el resultado es el más pequeño tipo real que incluya los tipos de ambos operandos. Cuando se usa como operador monádico, - denota inversión de signo, y + denota la identidad de la operación. Los operadores DIV y MOD se aplican solamente a operandos enteros. Estos están relacionados por las siguientes fórmulas definidas para cualquier x y divisor positivo y:
x = (x DIV y) * y + (x MOD y)
0 <= (x MOD y) < y
Ejemplos:
x y x DIV y x MOD y
5 3 1 2
-5 3 -2 1
- Operadores de conjunto
+ unión
- diferencia (x - y = x * (-y))
* intersección
/ conjunto diferencia simétrico (x / y = (x - y) + (y - x))
Los operadores de conjunto se aplican en operandos del tipo SET y producen un resultado de tipo SET. El signo menos monádico denota el complemento de x, con lo que -x denota el conjunto de enteros entre 0 y MAX(SET) que no son elementos de x. Los operadores de conjunto no son asociativos ((a + b) - c # a + (b - c)).
Un constructor de conjunto define el valor de un conjunto mediante el listado de sus elementos entre llaves ({}). Los elementos deben ser enteros en el rango 0...MAX(SET). Un rango a...b denota todos los enteros en el intervalo [a, b].
- Relaciones.
= igual
# distinto
< menor
<= menor o igual
> mayor
>= mayor o igual
IN miembro de un conjunto
IS test de tipo
Las relaciones devuelven un resultado de tipo BOOLEAN. Las relaciones =, #, <, <=, > y >= se aplican a tipos numéricos, CHAR, cadenas de caracteres y tablas de caracteres terminadas con 0X. Las relaciones = y # también se aplican a tipos SET y BOOLEAN, así como a tipos puntero y de procedimiento (incluido el valor NIL). x IN s significa "x es un elemento de s". x debe ser de tipo entero, y s de tipo SET. v IS t significa "el tipo dinámico de v es T (o una extensión de T)" y es llamado test de tipo. Esto es aplicable si
1. v es un parámetro variable de tipo registro o v es un puntero, y si
2. T es una extensión del tipo estático de v.
Ejemplos de expresiones:
1991 INTEGER
i DIV 3 INTEGER
~p OR q BOOLEAN
(i + j) * (i - j) INTEGER
s - {8, 9, 13} SET
i + x REAL
a[i + j] * a[i - j] REAL
(0 <= i) & (i < 100) BOOLEAN
t.key = 0 BOOLEAN
k IN {i..j - 1} BOOLEAN
w[i].name <= "John" BOOLEAN
t IS CenterNode BOOLEAN
Sentencias
Las sentencias denotan acciones. Existen sentencias simples y estructuradas. Las sentencias simples no están compuestas de ninguna parte que sea ella misma una sentencia. Son la asignación, la llamada a procedimiento y las sentencias return y exit. Las sentencias estructuradas están compuestas de partes que son ellas mismas sentencias. Se usan para expresar ejecuciones secuenciales, condicionales, selectivas y repetitivas. Una sentencia puede ser también vacía, en cuyo caso no denota acción alguna. La sentencia vacía se incluye en orden de relajar las reglas de puntuación en las secuencias de sentencias.
Asignaciones
Las asignaciones reemplazan el valor actual de una variable por un nuevo valor especificado por una expresión. La expresión debe ser de asignación compatible con la variable. El operador de asignación se escribe como ":=".
Si una expresión e de tipo Te es asignada a una variable v de tipo Tv, sucede los siguiente:
1. Si Tv y Te son del tipo registro, sólo se asignarán aquellos campos de Te que también estén en Tv (proyección); el tipo dinámico de v debe ser el mismo que el tipo estático de e, y no cambia con la asignación;
2. Si Tv y Te son del tipo puntero, el tipo dinámico de v se convierte en el tipo dinámico de e;
3. Si Tv es del tipo ARRAY n OF CHAR, y e es una cadena de caracteres de longitud m < n, v[i] será ei para i = 0...m - 1 y v[m] será 0X.
Ejemplos de asignaciones:
i := 0
p := i = j
x := i + 1
k := log2(i + j)
F := log2
s := {2, 3, 5, 7, 11, 13}
a[i] := (x + y) * (x - y)
t.key := i
w[i + 1].name := "John"
t := c
Llamadas a procedimiento
Una llamada a procedimiento activa un procedimiento. Esta puede contener una lista de parámetros actuales que reemplazan los correspondientes parámetros formales definidos en la declaración del procedimiento. La correspondencia se establece por posición entre los parámetros pasados y la lista de parámetros formales. Hay dos tipos de parámetros: por valor y por variable.
Si un parámetro formal es por variable, el parámetro pasado debe ser una variable. Si es un elemento de una estructura variable, el selector de componente es evaluado cuando se puede realizar la substitución del parámetro actual/formal, es decir antes de la ejecución del procedimiento. Si un parámetro formal en un parámetro por valor, el correspondiente parámetro puede ser una expresión. Esta expresión es evaluada antes de la activación del procedimiento, y el valor resultante es asignado al parámetro formal.
Ejemplos:
WriteInt(i * 2 + 1)
INC(w[k].count)
t.Insert("John")
Declaración de secuencias
La declaración de secuencias denota la secuencia de acciones especificadas por el componente de declaración, separados por un punto y coma.
Declaración IF
La declaración IF especifica la ejecución condicional de declaraciones de secuencias. La expresión booleana precede a una declaración de secuencia que es denominada su guardia (guard). Los guardias son evaluados en secuencia según aparecen, hasta que se obtiene el valor TRUE (verdadero), en cuyo caso se ejecutan las declaraciones de secuencias asociadas. Si no se satisface la condición, la ejecución continua a partir del símbolo ELSE (si existe).
Ejemplo:
IF (ch >= "A") & (ch <=
"Z") THEN ReadIdentifier
ELSIF (ch >= "0") & (ch
<= "9") THEN ReadNumber
ELSIF (ch = "'") OR (ch = '"')
THEN ReadString
ELSE SpecialCharacter
END;
Declaración CASE
La declaración CASE especifica la selección y ejecución de una secuencia de declaraciones acorde con el valor de una expresión. Primero se evalúa la expresión y después se ejecuta la secuencia correspondiente, según se corresponda el valor obtenido con la etiqueta de la secuencia a ejecutar. El valor de la expresión ha de ser siempre de tipo entero, y que incluya los tipos de todas las etiquetas del CASE; o tanto la expresión como las etiquetas deben ser del tipo CHAR. Las etiquetas son constantes, y no pueden estar repetidas. Si el valor de una expresión no tiene su etiqueta correspondiente, se ejecuta la secuencia declarada tras el símbolo ELSE (si existe); o, si no existe, se aborta el programa.
Ejemplo:
CASE ch OF
"A" .. "Z":
ReadIdentifier
| "0" .. "9": ReadNumber
| "'", '"': ReadString
ELSE SpecialCharacter
END
Declaración WHILE
Especifica la ejecución repetida de una secuencia de declaraciones mientras la expresión de tipo BOOLEAN (su guardia) de TRUE. La guardia es comprobada antes de cada ejecución de la secuencia de declaraciones.
Ejemplos:
WHILE i > 0 DO
i := i DIV
2;
k := k +
1
END
WHILE (t # NIL) & (t.key
# i) DO
t := t.left
END
Declaración REPEAT
Especifica la ejecución repetida de una secuencia de declaraciones hasta que se satisface la condición especificada en una expresión de tipo BOOLEAN. La secuencia de declaraciones se ejecuta al menos una vez.
Ejemplo:
REPEAT
i := i DIV
2
UNTIL i < 1
Declaración FOR
Especifica la ejecución repetida de una secuencia de declaraciones un número fijo de veces mientras una progresión de valores se asigna a una variable de tipo entero llamada variable de control de la declaración FOR.
El siguiente ejemplo:
FOR v := inicio TO final BY paso DO declaraciones END
es equivalente a:
temp := final; v := inicio;
IF paso > 0 THEN
WHILE v <=
temp DO declaraciones; v := v + paso END
ELSE
WHILE v >=
temp DO declaraciones; v := v + paso END
END
paso es del mismo tipo que v. paso debe ser una expresión constante distinta de cero. Si no se especifica paso, se asume que es 1.
Ejemplos:
FOR i:= 0 TO 79 DO k := k + a[i] END
FOR i := 79 TO 1 BY -1 DO a[i] := a[i - 1] END
Declaración LOOP.
Especifica la ejecución repetida de una secuencia de declaraciones. Esta finaliza cuando se ejecuta una sentencia EXIT dentro de la secuencia.
Ejemplo:
LOOP
ReadInt(i);
IF i <
0 THEN EXIT END;
WriteInt(i)
END
Sentencias RETURN y EXIT.
Una sentencia RETURN indica la terminación de un procedimiento. En caso de ser una función, RETURN debe estar seguido de una expresión, que es el valor devuelto por la misma. El tipo de la expresión debe ser compatible con el tipo del resultado especificado en la cabecera del procedimiento.
En los procedimientos de función se requiere la presencia de la sentencia RETURN indicando el valor del resultado, mientras que en los procedimientos propiamente dichos se infiere el punto de retorno como situado al final del cuerpo del procedimiento, por lo que una invocación explícita de esta sentencia aparece como un adicional (probablemente excepcional) punto de terminación.
Una sentencia EXIT especifica la terminación de un bucle anidado y la continuación con la secuencia de instrucciones que siguen a dicho bucle. La sentencia EXIT es contextual, aunque no sintácticamente asociada con el bucle que lo contiene.
Declaración WITH
Ejecuta una secuencia de instrucciones dependiendo del resultado de un test de tipo y aplicando una guardia de tipo para cada ocurrencia de la variable testada dentro de la sencuencia de instrucciones.
Si v es un parámetro variable de un tipo registro o una variable de puntero, y si es de un tipo estático T0, la secuencia:
WITH v: T1 DO S1 |v: T2 DO S2 ELSE S3 END
tiene el siguiente significado: si el tipo dinámico de v es T1, entonces se ejecuta la secuencia S1 donde v es contemplado como si tuviese el tipo estático T1; si no, si el tipo dinámico de v es T2, entonces se ejecuta la secuencia S2 donde v es contemplado como si tuviese el tipo estático T2; si no, se ejecuta S3. T1 y T2 deben ser extensiones de T0. Si no se satisface ninguna prueba de tipo, y si no se ha especificado una cláusula ELSE, se abortará el programa.
Ejemplo:
WITH t: CentreTree DO i := t.width; c := t.subnode END
Declaración de procedimiento
Una declaración de procedimiento consiste en una cabecera de procedimiento y un cuerpo del procedimiento. La cabecera especifica el identificador de procedimiento y los parámetros formales. Para los procedimientos de tipo también se especifica el parámetro receptor. El cuerpo contiene las declaraciones y sentencias. El identificador de procedimiento se repite al final de la declaración del procedimiento.
Hay dos tipos de procedimientos: los procedimientos propios y los procedimientos de función. Este último se activa por un designador de función que está constituido de una expresión y que devuelve un resultado que es un operando de la expresión. Los procedimientos propios se activan mediante una llamada al procedimiento. Un procedimiento es de función si en sus parámetros formales se especifica un tipo de resultado. El cuerpo de una función debe contener una sentencia RETURN que defina su resultado.
Todas las constantes, variables, tipos y procedimientos declarados dentro del cuerpo de un procedimiento son locales al procedimiento. Desde que los procedimientos pueden ser declarados como objetos locales también, las delcaraciones de procedimiento pueden anidarse. La llamada de un procedimiento dentro de su declaración implica una activación recursiva.
Los objetos declarados en el entorno de un procedimiento son también visibles en aquellas partes del procedimiento en que no coincidan con la declaración de un objeto local con el mismo nombre.
Si una declaración de procedimiento especifica un parámetro de recepción, dicho procedimiento es considerado limitado a un tipo. Una declaración adelantada sirve para permitir referencias adelantadas a un procedimiento cuya declaración aparece después en el texto. La lista de parámetros formales de una declaración adelantada y de su posterior declaración deben coincidir.
Parámetros formales
Los parámetros formales son identificadores declarados en la lista de parámetros formales de un procedimiento. Se corresponden con los parámetros especificados en la llamada al procedimiento. La correspondencia entre los parámetros formales y los utilizados se establece durante la llamada al procedimiento. Existen dos tipos de parámetros: por valor y variables; indicados en la lista de parámetros formales por la ausencia o presencia de la palabra clave VAR. Los parámetros por valor son variables locales, cuyo valor inicial se indica durante la llamada al procedimiento. Los parámetros variables son un alias de la variable utilizada como parámetro en la llamada al procedimiento, por lo que cualquier modificación de su valor que se realice dentro del procedimiento se verá reflejado en la variable pasada como parámetro en la llamada al procedimiento. El ámbito de visibilidad de dichas variables llegará hasta el final del procedimiento donde se declaren. Un procedimiento de función sin parámetros debe tener una lista de parámetros vacía. El tipo del resultado de un procedimiento de función no puede ser una matriz ni un registro.
Siendo Tf el tipo del parámetro formal f (no una matriz abierta), y Ta el tipo del correspondiente parámetro pasado a. Para los parámetros variables, Ta debe ser el mismo que Tf, o Tf debe ser un tipo registro y Ta una extensión de Tf. Para parámetros por valor, a debe ser compatible con f. Si Tf es una matriz abierta, entonces a debe ser una matriz compatible con f. Las longitudes de f se corresponderán con las de a.
Ejemplos de declaraciones de procedimiento:
PROCEDURE ReadInt(VAR x: INTEGER);
VAR i: INTEGER; ch: CHAR;
BEGIN
i := 0;
Read(ch);
WHILE ("0"
<= ch) & (ch >= "9") DO
i := 10 * i + (ORD(ch) - ORD("0"));
Read(ch)
END;
x := i;
END ReadInt
PROCEDURE WriteInt(x: INTEGER);
(* 0 <= x <= 100000 *)
VAR i: INTEGER; buf: ARRAY 5
OF INTEGER;
BEGIN
i := 0;
REPEAT
buf[i] := x MOD 10;
x := x DIV 10;
INC(i)
UNTIL x =
0;
REPEAT
DEC(i);
Write(CHR(buf[i] + ORD("0")))
UNTIL i =
0;
END WriteInt
PROCEDURE WriteString(s: ARRAY
OF CHAR);
VAR i: INTEGER;
BEGIN
i := 0;
WHILE (i
< LEN(s)) & (s[i] # 0X) DO
Write(s[i]);
INC(i)
END
END WriteString
PROCEDURE log2(x: INTEGER): INTEGER;
VAR y: INTEGER; (* se asume
x > 0 *)
BEGIN
y := 0;
WHILE x >
1 DO x := x DIV 2; INC(y) END;
RETURN y
END log2
Procedimientos de tipo
Los procedimientos declarados globalmente pueden ser asociados con un tipo registro en el mismo módulo. Los procedimientos se dice que están limitados al tipo registro. La ligazón se expresa por el tipo del receptor en la cabecera de la declaración del procedimiento. El receptor puede ser un parámetro variable de tipo registro T o un parámetro por valor del tipo POINTER TO T (donde T es un tipo registro). El procedimiento está limitado al tipo T y es considerado local a él.
Si un procedimiento P está limitado a un tipo T0, también está implícitamente limitado a cualquier tipo T1 que sea una extensión de T0. Sin embargo, un procedimiento P' (con el mismo nombre que P) puede ser explícitamente limitado a T1 en el caso de que ¿deroge? la ligazón de P. P' es considerada una redefinición de P para T1. Los parámetros formales de P y P' deben coincidir. Si P y T1 se exportan, P' puede ser también exportada.
Si v es un designador y P es un procedimiento de tipo, entonces v.P denota que el procedimiento P está limitado al tipo dinámico de v (enlace dinámico). Nótese que este puede ser un procedimiento diferente que el asociado al tipo estático de v. v es pasado a los receptores de P' conforme a las reglas de pase de parámetros especificados en la sección 10.
Si r es un parámetro receptor declarado con un tipo T, r.P^ denota el (redefinido) procedimiento de P ligado al tipo base de T.
En la declaración previa de un procedimiento de tipo el parámetro receptor debe ser del mismo tipo que en la declaración del procedimiento actual. La lista de parámetros formales de ámbas declaraciones deben coincidir.
Ejemplos:
PROCEDURE (t: Tree) Insert (node:
Tree);
VAR p, father: Tree;
BEGIN
p := t;
REPEAT father
:= p;
IF node.key = p.key THEN RETURN END;
IF node.key < p.key THEN p := p.left
ELSE p := p.right
END
UNTIL p =
NIL;
IF node.key
< father.key THEN father.left := node
ELSE father.right
:= node
END;
node.left
:= NIL;
node.right
:= NIL
END Insert;
PROCEDURE (t: CenterTree) Insert
(node: Tree); (* redefinición *)
BEGIN
WriteInt(node(CenterTree).width);
t.Insert^(node)
(* se llama al procedimiento Insert ligado a Tree *)
END Insert;
Procedimientos predeclarados
La siguiente tabla lista los procedimientos predeclarados. Algunos son procedimientos genéricos, es decir, que se aplican a diversos tipos de operandos. v indica una variable; x y n, expresiones; y T, tipo.
Procedimientos de función:
Nombre | Tipo del argumento | Tipo del resultado | Función |
ABS(x) | tipo numérico | tipo de x | valor absoluto |
ASH(x, n) | x, n: tipo entero | LONGINT | desplazamiento aritmético (x * 2 elevado a n) |
CAP(x) | CHAR | CHAR | x es una letra: corresponde a su mayúscula |
CHR(x) | tipo entero | CHAR | devuelve el caracter con el número ordinal x |
ENTIER(x) | tipo real | LONGINT | entero largo no mayor que x |
LEN(v, n) | v: matriz; n: constante entera | LONGINT | longitud de v en su dimensión n (primera dimensión = 0) |
LEN(v) | v: matriz | LONGINT | es lo mismo que LEN(v, 0) |
LONG(x) | SHORTINT, INTEGER, REAL | INTEGER, LONGINT, LONGREAL | identidad |
MAX(t) | T=tipo básico, T=SET | T, INTEGER | valor máximo del tipo T, máximo elemento de un conjunto |
MIN(t) | T=tipo básico, T=SET | T, INTEGER | valor mínimo del tipo T, 0 |
ODD(x) | tipo entero | BOOLEAN | x MOD 2 = 1 |
ORD(x) | CHAR | INTEGER | número ordinal de x |
SHORT(x) | LONGINT, INTEGER, LONGREAL | INTEGER, SHORTINT, REAL (posible truncamiento) | identidad |
SIZE(t) | cualquier tipo | INTEGER | número de bytes requerido por T |
Procedimientos propios
Nombre | Tipos del argumento | Función |
ASSERT(x) | x: expresión booleana | finaliza la ejecución del programa si no se cumple x |
ASSERT(x,n) | x: expresión booleana; n: constante entera | finaliza la ejecución del programa si no se cumple x; devuelve n |
COPY(x, v) | x: tabla de caracteres, cadena; v: tabla de caracteres | v := x |
DEC(v) | tipo entero | v := v - 1 |
DEC(v, n) | v, n: tipo entero | v := v - n |
EXCL(v, x) | v: SET; x: tipo entero | v := v - x |
HALT(n) | constante entera | finaliza la ejecución del programa; devuelve n |
INC(v) | tipo entero | v := v + 1 |
INC(v, n) | v, n: tipo entero | v := v + n |
INCL(v, x) | v: SET; x: tipo entero | v := v + x |
NEW(v) | puntero a registro o matriz fija | dispone un puntero a v^ |
NEW(v, x0, ..., xn) | v: puntero a una matriz abierta; xi: tipo entero | x0 ... xn |
COPY permite la asignación de una cadena o matriz de caracteres conteniendo el caracter de terminación 0X a otra matriz de caracteres. Si es necesario, el valor asignado se trunca a la longitud del destino menos 1. El destino debe contener siempre 0X como terminador. En ASSERT(x, n) y HALT(n), la interpretación de n depende de la implementación del sistema subyacente.
Módulos
Un módulo es una colección de declaraciones de constantes, tipos, variables y procedimientos, junto con una secuencia de declaraciones con el propósito de asignar valores iniciales a las variables. Un módulo constituye un texto que es compilable como una unidad.
La lista de importación especifica los nombres de los módulos importados. Si un módulo A es importado por un módulo M, y A exporta un identificador x, entonces x es referenciado como A.x dentro de M. Si A es importado como B := A, el objeto x es referenciado como B.x. Esto permite nombres de alias cortos. Un módulo no puede importarse a sí mismo. Los identificadores que deban exportarse (es decir, que deban ser visibles en los módulos clientes), deben marcarse con un signo de exportación en su declaración.
La secuencia de instrucciones que siguen al símbolo BEGIN se ejecutarán cuando el módulo se añada al sistema (es decir, cuando se cargue). De esto se desprende que la importación cíclica de módulos no está permitida. Los procedimientos individuales exportados y sin parámetros, se pueden activar por el sistema como si fueran comandos.
MODULE Trees;
(* exporta: Tree, Node, Insert,
Search, Write, NewTree *)
(* exporta como sólo
lectura: Node.name *)
IMPORT Texts, Oberon;
TYPE
Tree* = POINTER
TO Node;
Node* = RECORD
name- : POINTER TO ARRAY OF CHAR;
left, right : Tree
END;
VAR w: Texts.Writer;
PROCEDURE (t: Tree) Insert* (name:
ARRAY OF CHAR);
VAR p, father: Tree;
BEGIN
p := t;
REPEAT
father := p;
IF name = p.name^ THEN RETURN END;
IF name < p.name^ THEN p := p.left
ELSE p := p.right
END
UNTIL p =
NIL;
NEW(p); p.left
:= NIL; p.right := NIL;
NEW(p.name,
LEN(name) + 1);
COPY(name,
p.name^);
IF name <
father.name^ THEN father.left := p
ELSE father.right
:= p
END;
END Insert;
PROCEDURE (t: Tree) Search* (name:
ARRAY OF CHAR): Tree;
VAR p: Tree;
BEGIN
p := t;
WHILE (p #
NIL) & (name # p.name^) DO
IF name = p.name^ THEN p := p.left
ELSE p := p.right
END
END;
RETURN p
END Search;
PROCEDURE (t: Tree) Write*;
BEGIN
IF t.left
# NIL THEN t.left.Write END;
Texts.WriteString(w,
t.name^);
Texts.WriteLn(w);
Texts.Append(Oberon.Log,
w.buf);
IF t.right
# NIL THEN t.rigth.Write END
END Write;
PROCEDURE NewTree* (): Tree;
VAR t: Tree;
BEGIN
NEW(t); NEW(t.name,
1);
t.name[0]
:= 0X;
t.left :=
NIL; t.right := NIL;
RETURN t
END NewTree;
BEGIN
Texts.OpenWriter(w)
END Trees.
Definición de términos
Tipos enteros: SHORTINT, INTEGER, LONGINT
Tipos reales: REAL, LONGREAL
Tipos numéricos: Tipos enteros y tipos reales
Mismos tipos
Dos variables a y b con los tipos Ta y Tb son del mismo tipo si
1. Ta y Tb están ámbos denotados por el mismo identificador de tipo, o
2. Ta es declarado igual a Tb en una declaración de tipo de la forma Ta = Tb, o
3. a y b aparecen en la misma lista de identificadores en una variable, campo de registro o una declaración de parámetro formal y no son matrices abiertas.
Tipos iguales
Dos tipos Ta y Tb son iguales si
1. Ta y Tb tienen el mismo tipo, o
2. Ta y Tb tienen tipos de matriz abierta con iguales tipos de elemento, o
3. Ta y Tb tiene tipos de procedimiento cuyas listas de parámetros formales coinciden.
Inclusión de tipo
Los tipos numéricos incluyen (los valores de) tipos numéricos menores según la siguiente jerarquía
LONGREAL Ê REAL Ê LONGINT Ê INTEGER Ê SHORTINT
Extensión de tipo (del tipo base)
Dado una declaración de tipo Tb = RECORD (Ta) ... END, Tb es una extensión directa de Ta, y Ta es un tipo de base directo de Tb. Un tipo Tb es una extensión de un tipo Ta (Ta es un tipo base de Tb) si
1. Ta y Tb tienen el mismo tipo, o
2. Tb es una extensión directa de una extensión de tipo Ta
Si Pa = POINTER TO Ta y Pb = POINTER TO Tb, Pb es una extensión de Pa (Pa es un tipo base de Pb) si Tb es una extensión of Ta.
Asignación compatible
Una expresión e de tipo Te es compatible en asignación con una variable v de tip Tv si se cumple una de las siguientes condiciones:
1. Te y Tv son del mismo tipo;
2. Te y Tv son tipos numéricos y Tv incluye a Te.
3. Te y Tv son del tipo registro y Te es una extensión de Tv, y el tipo dinámico de v es Tv;
4. Te y Tv son del tipo puntero y Te es una extensión de Tv;
5. Tv es un puntero o un procedimiento de tipo y e es NIL;
6. Tv es ARRAY n OF CHAR, e es una constante de cadena con m caracteres, y m < n;
7. Tv es un procedimiento de tipo y e es el nombre de un procedimiento cuyos parámetros formales casan con los de Tv.
Matriz compatible
Un parámetro a de tipo Ta es de matriz compatible con un parámetro formal f de tipo Tf si
1. Tf y Ta son del mismo tipo, o
2. Tf es una matriz abierta, Ta es una matriz, y los tipos de sus elementos son de matriz compatible, o
3. Tf es ARRAY OF CHAR y a es una cadena de caracteres
Expresiones compatibles
Para un operador dado, los tipos de sus operandos son expresiones compatibles
si son conformes con la siguiente tabla (que muestra también el
tipo de resultado de la expresión). Las matrices de caracteres,
para poder ser comparadas deben contener 0X como terminador. El tipo T1
debe ser una extensión del tipo T0:
Operador | Primer operando | Segundo operando | Tipo del resultado |
+ - * | numérico | numérico | el tipo numérico más pequeño que incluya a ambos operandos |
/ | numérico | numérico | el tipo real más pequeño que incluya a ambos operandos |
+ - * / | SET | SET | SET |
DIV MOD | entero | entero | el tipo numérico más pequeño que incluya a ambos operandos |
OR & ~ | BOOLEAN | BOOLEAN | BOOLEAN |
= # < <= > >= | numérico
CHAR cadena o matriz |
numérico
CHAR cadena o matriz |
BOOLEAN
BOOLEAN BOOLEAN |
= # | BOOLEAN
SET NIL, POINTER TO T0 o T1 NIL, procedimiento de tipo T |
BOOLEAN
SET NIL, POINTER TO T0 o T1 NIL, procedimiento de tipo T |
BOOLEAN
BOOLEAN BOOLEAN BOOLEAN |
IN | entero | SET | BOOLEAN |
IS | puntero, registro | puntero, registro | BOOLEAN |
Correspondencia entre listas de parámetros formales
Dos listas de parámetros formales casan si
1. tienen el mismo número de parámetros, y
2. tienen el mismo tipo de resultado de la función o ninguno, y
3. los parámetros en las correspondientes posiciones tienen el mismo tipo, y
4. los parámetros en las correspondientes posiciones tienen ambos
(¿los mismos?) valores o variables parámetros.
El módulo SYSTEM
El módulo SYSTEM contiene ciertos tipos y procedimientos que son necesarios para implementar operaciones de bajo nivel particulares de una implementación o computadora dada. Este incluye, por ejemplo, facilidades para acceder a dispositivos que son controlados por el ordenador, o para romper las reglas de compatibilidad de tipo impuestas por la definición del lenguaje. Es altamente recomendable restringir su uso a módulos específicos (llamados módulos de bajo nivel). Dichos módulos son inherentemente no portables, pero fácilmente reconocibles porque el identificador SYSTEM aparece en su lista de importación. Las siguientes especificaciones se ajustan a la implementación de Oberon-2 en una computadora Ceres.
El módulo SYSTEM exporta un tipo BYTE con las siguientes características: las variables de tipo CHAR o SHORTINT pueden ser asignadas a variables de tipo BYTE. Si un parámetro formal variable es del tipo ARRAY OF BYTE entonces el parámetro correspondiente pasado puede ser de cualquier tipo.
Otro tipo exportado por el módulo SYSTEM es el tipo PTR. Las variables del cualquier tipo puntero pueden ser asignadas a variables de tipo PTR. Si un parámetro variable formal es de tipo PTR, el parámetro pasado puede ser de cualquier tipo puntero.
Los procedimientos contenidos en el módulo SYSTEM se listan en la siguiente tabla. Muchos de ellos corresponden a instrucciones simples compiladas como código in-line. v indica una variable, x, y,a y n indican expresiones, y T indica tipos.
Procedimientos de función
Nombre | Tipos de los argumentos | Tipo del resultado | Función |
ADR(v) | cualquiera | LONGINT | dirección de la variable v |
BIT(a, n) | a: LONGINT; n: INTEGER | BOOLEAN | bit n de Mem[a] |
CC(n) | constante entera | BOOLEAN | condición n (0 = n = 15) |
LSH(x, n) | x: entero, CHAR, BYTE; n: entero | tipo de x | desplazamiento lógico |
ROT(x, n) | x: entero, CHAR, BYTE; n: entero | tipo de x | rotación |
VAL(T, x) | T, x: cualquier tipo | T | interpretado como de tipo T |
Procedimientos
Nombre | Tipos de los argumentos | Función |
GET(a, v) | a: LONGINT; v: cualquier tipo básico, puntero o procedimiento de tipo | v := Mem[a] |
PUT(a, x) | a: LONGINT; x: cualquier tipo básico, puntero o procedimiento de tipo | Mem[a] := x |
GETREG(n, v) | n: constante entera; v: cualquier tipo básico, puntero o procedimiento de tipo | v := Registro n |
PUTREG(n, x) | n: constante entera; x: cualquier tipo básico, puntero o procedimiento de tipo | Registro n := x |
MOVE(a0, a1, n) | a0, a1: LONGINT; n: entero | Mem[a1...a1 + n - 1] := Mem[a0...a0 + n - 1] |
NEW(v, n) | v: cualquier puntero; n: entero | asigna un bloque de almacenamiento de n bytes, guardanto su dirección de memoria en v |