Introducción

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.

1. Identificadores son secuencias de letras y dígitos. El primer carácter debe ser una letra.

Identificador = letra {letra | dígito}

Ejemplos: x    Scan    Oberon2    GetSymbol    primeraLetra
 

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.
 
 

Declaraciones y reglas de ámbito

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:

1. Un identificador no puede usarse para más de un objeto dentro de un determinado ámbito (o sea, que no se puede duplicar la declaración).

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.

Un identificador declarado en un bloque de módulo debe ser seguido por una marca de exportación (“*” o “-“) en su declaración para indicar que se exporta. Un identificador x exportado por un módulo M puede ser usado en otros módulos. El identificador se denota como M.x en dicho módulo y es denominado identificador cualificado. Los identificadores marcados con “-“ en su declaración serán de solo-lectura en los módulos que lo importen.

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:

1.    BOOLEAN       los valores TRUE y FALSE
2.    CHAR          los caracteres del juego ASCII extendido (0X...FFX)
3.    SHORTINT      los enteros entre MIN(SHORTINT) y MAX(SHORTINT)
4.    INTEGER       los enteros entre MIN(INTEGER) y MAX(INTEGER)
5.    LONGINT       los enteros entre MIN(LONGINT) y MAX(LONGINT)
6.    REAL          los números reales entre MIN(REAL) y MAX(REAL)
7.    LONGREAL      los números reales entre MIN(LONGREAL) y MAX(LONGREAL)
8.    SET           el conjunto de enteros entre 0 y MAX(SET)
Los tipos 3 a 5 son tipos enteros, los tipos 6 y 7 son tipos reales, y ambos son tipos numéricos. Estos forman una jerarquía; los tipos mayores incluyen (el valor de) los tipos menores:

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