Oberon-2

From Wikipedia, the free encyclopedia

Oberon-2
Paradigm: imperative, structured, modular, object-oriented
Appeared in: 1991
Designed by: Niklaus Wirth, Hanspeter Mössenböck
Typing discipline: strong, static
Influenced by: Oberon programming language
This article refers to the newer Oberon-2 programming language.
For the older original Oberon language, see Oberon programming language

Oberon-2 is an extension of the original Oberon programming language that adds limited reflection and object-oriented programming facilities, and a few other features like FOR loop, open arrays, and read-only field export.

It was developed in 1991 at ETH Zurich by Niklaus Wirth and Hanspeter Mössenböck, who is now at Institut für Systemsoftware (SSW) of the University of Linz, Austria. Oberon-2 is a superset of Oberon, and is fully compatible with it. Oberon-2 was a redesign of Object Oberon.

Oberon-2 introduced limited reflection, methods ("type bound procedures"), and single inheritance ("type extension") without interfaces or mixins. Method calls were resolved at run-time, and limited polymorphism was possible by either overloading methods or by explicit typecase ("with" statement).

Compared to fully object-oriented programming languages like Smalltalk, in Oberon-2 basic types are not objects, classes are not objects, many operations are not methods, there is no message passing (to a certain extent it can be emulated by reflection), and polymorphism is limited to subclasses of a common class (no duck typing like in Smalltalk/Ruby, and it's not possible to define interfaces like in Java). Oberon-2 does not support encapsulation at object/class level, but modules can be used for this purpose.

Reflection in Oberon-2 does not use meta-objects, but simply reads from a disk file metadata generated by a compiler. It could therefore be implemented almost entirely at library level, without changing the language code.

Contents

[edit] History of the Oberon languages

The original Oberon was designed to be a 'safe' language; it employs array bounds checking, garbage collection and strong type checking. These features, particularly ones which enable logic errors to be detected as early as possible (i.e. at compile-time), can significantly reduce the number of bugs occurring in a program at runtime. However, some features included in other languages in an attempt to reduce bugs (e.g. enumerations and programmer-defined ranges on integers), were omitted. Consequently, more care should be taken by the programmer, when working with numeric expressions, to avoid logic errors.

Oberon was intended to make mistakes harder in part by making code less opaque, and in part because features not included cannot be misused. This approach can be taken even further, as in APL, which is both exceptionally terse and renowned for being less than easy to understand, but Oberon was deliberately constructed to not oversimplify.

As this is an intent whose success cannot be easily quantified, there remains some disagreement that Oberon has achieved its intended goals in this respect. One objection to its strategy of language design simplification was expressed by Jean Ichbiah, the architect of Ada when Wirth criticized Ada for being too big; he responded "There are times when Wirth believes in small solutions for big problems. I don't believe in that sort of miracle. Big problems need big solutions!" Oberon developers have even felt that Oberon went too far in this respect -- Oberon-2 returned the 'FOR' statement to that version of the language.

It can be argued that failure to include a feature may force the programmer to reimplement the feature in his code, leading to multiple 'wheel reinvention' and consequent problems. Libraries can mitigate this -- more or less -- effectively depending on the feature and a language's graceful use of such libraries. Java is an example of a relatively simple language (though far less so than Oberon) embedded in large standard libraries. (Oberon has a much smaller standard library than Java.) As much of the effort of learning any language is learning the standard libraries, Ichbiah's objection above can be extended to a strategy of simplification by moving features from the core language into standard libraries. Wirth, and Oberon fans, argue that Oberon has essentially, and effectively, avoided this problem.

[edit] Example Oberon-2 code

The following Oberon-2 code would implement a simple list class :-

MODULE Lists;

    (*** declare global constants, types and variables ***)

    TYPE
        List* = POINTER TO ListNode;
        ListNode = RECORD
            value : INTEGER;
            next : List;
        END;

    (*** declare procedures ***)

    PROCEDURE (VAR list: List) add* (val : INTEGER);
    BEGIN
        IF list = NIL THEN
            NEW(list);             (* create record instance *)
            list.value := val;
        ELSE
            list.next.add(val);    (* recursive call to .add *)
        END
    END add;
END Lists.

[edit] Features of the original Oberon

[edit] Key characteristics

The following features characterise the Oberon language :

  • Pascal-like, but more consistent, syntax
  • Type-extension with strong type-checking
  • Modules with type-checked interfaces and separate compilation
  • Compatibility between all numeric types (mixed expressions)
  • String operations
  • Support for system programming

[edit] Visibility flags

Global variables, types, constants, and procedures are by default only visible within the declaring module. They may be made public to other modules by suffixing them with a visibility flag, namely an asterisk (*) for read-write permission. The default was chosen to ensure safety in case a flag was inadvertantly omitted.

Local variables, types, constants, and procedures are always visible only to the declaring procedure.

[edit] Call by reference or by value

Two possible modes are available for procedure parameters. Call-by-value (CBV) allows expressions to be used as parameters, so that the value of the expression is passed down to the procedure. Call-by-reference (CBR) allows variables to be used, so that the value of the variable may be changed by the procedure. A procedure may declare a CBR parameter by prefixing it with the VAR keyword.

[edit] Oberon-2 extensions to Oberon

[edit] Type-bound procedures

Procedures can be bound to a record (or pointer) type. They are equivalent to instance methods in object-oriented terminology.

[edit] Read-only export

The use of exported variables and record fields can be restricted to read-only access. This is shown with a "-" visibility flag.

[edit] Open arrays

Open arrays may be declared as formal parameter types, or as pointer base types.

[edit] FOR statement

The FOR statement of Pascal and Modula-2 was not implemented in Oberon. It is reintroduced in Oberon-2.

[edit] Run-time type checking

Oberon-2 provides several mechanisms for checking the dynamic type of an object. For example, where a Bird object might be instantiated to either a Duck or a Cuckoo, Oberon-2 allows the programmer to respond to the actual type of the object at run-time.

The first, most conventional, approach is to rely on the type binding system. The second approach is to use the WITH statement, which allows the dynamic subtype of a variable to be checked directly. In both cases, once the subtype has been identified, the programmer can make use of any type-bound procedures or variables that are appropriate to the subtype. Examples of these approaches are shown below.

Note that the form of WITH statement used in Oberon-2 is unrelated to the Pascal and Modula-2 WITH statement. This method of abbreviating access to record fields is not implemented in Oberon or Oberon-2.

Type binding

MODULE Ducks;
    IMPORT Birds;
    TYPE
        Duck* = RECORD(Birds.Bird) END;
            
    PROCEDURE makeSound*(bird: Duck; VAR result: ARRAY OF CHAR);
    BEGIN
        bird.swim; (* ducks only *)
        COPY("Quack!", result)
    END makeSound;
END Ducks.

MODULE Cuckoos;
    IMPORT Birds;
    TYPE
        Cuckoo* = RECORD(Birds.Bird) END;
            
    PROCEDURE makeSound*(bird: Cuckoo; VAR result: ARRAY OF CHAR);
    BEGIN
        bird.stealEggs; (* cuckoos only *)
        COPY("Cuckoo!", result)
    END makeSound;
END Cuckoos.

WITH statement

MODULE MultiBird;
    IMPORT Birds, Cuckoos, Ducks;

    PROCEDURE makeSound*(bird: Birds.Bird; VAR result: ARRAY OF CHAR);
    BEGIN
        WITH
          | bird : Cuckoos.Cuckoo DO
            bird.stealEggs; (* cuckoos only *)
            COPY("Cuckoo!", result)
          | bird : Ducks.Duck DO
            bird.swim; (* ducks only *)
            COPY("Quack!", result)
          ELSE
            COPY("Tweet!", result)
        END
    END makeSound;
END MultiBird.

A third approach is possible using the IS operator. This is a relation operator with the same precedence as equals (=), greater(>), etc. but which tests dynamic type. Unlike the two other approaches, however, it does not allow the programmer access to the subtype that has been detected.

[edit] Syntax

The development of the ALGOL - Pascal - Modula-2 - Oberon - Component Pascal language family is marked by a reduction in the complexity of the language syntax. The entire Oberon-2 language is described (Mössenböck & Wirth, 1993) using only 33 grammatical productions, as shown below.

Module        = MODULE ident ";" [ImportList] DeclSeq [BEGIN StatementSeq] END ident ".".
ImportList    = IMPORT [ident ":="] ident {"," [ident ":="] ident} ";".
DeclSeq       = { CONST {ConstDecl ";" } | TYPE {TypeDecl ";"} | VAR {VarDecl ";"}} {ProcDecl ";" | ForwardDecl ";"}.
ConstDecl     = IdentDef "=" ConstExpr.
TypeDecl      = IdentDef "=" Type.
VarDecl       = IdentList ":" Type.
ProcDecl      = PROCEDURE [Receiver] IdentDef [FormalPars] ";" DeclSeq [BEGIN StatementSeq] END ident.
ForwardDecl   = PROCEDURE "^" [Receiver] IdentDef [FormalPars].
FormalPars    = "(" [FPSection {";" FPSection}] ")" [":" Qualident].
FPSection     = [VAR] ident {"," ident} ":" Type.
Receiver      = "(" [VAR] ident ":" ident ")".
Type          = Qualident
              | ARRAY [ConstExpr {"," ConstExpr}] OF Type
              | RECORD ["("Qualident")"] FieldList {";" FieldList} END
              | POINTER TO Type
              | PROCEDURE [FormalPars].
FieldList     = [IdentList ":" Type].
StatementSeq  = Statement {";" Statement}.
Statement     = [ Designator ":=" Expr
              | Designator ["(" [ExprList] ")"]
              | IF Expr THEN StatementSeq {ELSIF Expr THEN StatementSeq} [ELSE StatementSeq] END
              | CASE Expr OF Case {"|" Case} [ELSE StatementSeq] END
              | WHILE Expr DO StatementSeq END
              | REPEAT StatementSeq UNTIL Expr
              | FOR ident ":=" Expr TO Expr [BY ConstExpr] DO StatementSeq END
              | LOOP StatementSeq END
              | WITH Guard DO StatementSeq {"|" Guard DO StatementSeq} [ELSE StatementSeq] END
              | EXIT
              | RETURN [Expr]
      ].        
Case          = [CaseLabels {"," CaseLabels} ":" StatementSeq].
CaseLabels    = ConstExpr [".." ConstExpr].
Guard         = Qualident ":" Qualident.
ConstExpr     = Expr.
Expr          = SimpleExpr [Relation SimpleExpr].
SimpleExpr    = ["+" | "-"] Term {AddOp Term}.
Term          = Factor {MulOp Factor}.
Factor        = Designator ["(" [ExprList] ")"] | number | character | string | NIL | Set | "(" Expr ")" | " ~ " Factor.
Set           = "{" [Element {"," Element}] "}".
Element       = Expr [".." Expr].
Relation      = "=" | "#" | "<" | "<=" | ">" | ">=" | IN | IS.
AddOp         = "+" | "-" | OR.
MulOp         = " * " | "/" | DIV | MOD | "&".
Designator    = Qualident {"." ident | "[" ExprList "]" | " ^ " | "(" Qualident ")"}.
ExprList      = Expr {"," Expr}.
IdentList     = IdentDef {"," IdentDef}.
Qualident     = [ident "."] ident.
IdentDef      = ident [" * " | "-"].

[edit] Implementations

Oberon-2 compilers maintained by ETH include versions for Windows, Linux, Solaris, Mac OS X.

There is an Oberon-2 Lex scanner and Yacc parser by Stephen J Bevan of Manchester University, UK, based on the one in the Mössenböck and Wirth reference. It is at version 1.4.

There is a release called Native Oberon which includes an operating system, and can directly boot on PC class hardware.

A .NET implementation of Oberon with the addition of some minor .NET-related extensions has also been developed at ETHZ.

Programmer's Oberon-2 Workbench (POW!) is a very simple programmer's workbench, which is provided with an Open Source version of Oberon-2. This compiles to Windows executables. Full source code is provided - the compiler is written in Oberon-2.

The Java to Oberon Compiler (JOB) was written at the University of Vologda in Russia. It produces object code in the form of Java class files (bytecode). Some JOB-specific classes are provided which are Java compatible, but which use a more Oberon-like component heirarchy.

[edit] References

  • "The Programming Language Oberon-2", H. Mössenböck, N. Wirth, Institut für Computersysteme, ETH Zürich, January 1992.
  • "Second International Modula-2 Conference", Sept 1991.

[edit] External links