Frequently Asked Questions on the Oberon Language

Note: Questions not specifically related with the Oberon language or the compiler are dealt with in the FAQ on the ETH Oberon system.

General

  1. What advantage do you see in using Oberon rather than other widespread programming languages?
Style
  1. What is the etymology of Reader, Scanner and Writer?
  2. What naming conventions can be recommended?
Types
  1. What is the size of the record?
  2. Why can't I assign a variable to another one of the same type?
  3. How does the compiler treat whole number constants?
Programming hints
  1. What is the longest identifier that can be used in Oberon sources?
  2. How can I handle 16-bit unsigned numbers in Oberon?
  3. How do I cast a variable?
  4. How do I cast REAL variables to INTEGER very fast? ( quasi-realtime)
  5. Is there a way to improve the speed of computation for a realtime application?
  6. How do I spread random numbers as uniformly as possible?
  7. Are there any bitwise operators available in Oberon?
  8. How do I write a constant value directly to an memory?
  9. Variable initialization can be at fault
  10. Is there a portable way to identify the caller of a procedure?
Compiler
  1. How do I locate an error in the source text?
  2. Why does the \z option cause the ceation of a larger code?
  3. Is there a test set for Oberon compilers?
Built-in assembler
  1. Where do I find assembler documentation?
  2. What is the order of fields in a record?
  3. The compiler's behavior is unusual when casting anything to BOOLEAN.
  4. What is the correct syntax for operating on SYSTEM.BYTE?
  5. How to call a procedure from an assembler routine?
  6. How do I cast a large number of variables very fast?
  7. How to use in-line procedures

General

  1. What advantage do you see in using Oberon rather than other widespread programming languages?
    A:
    The following was reported by Antonio Cisternino :

    Reading the old book "Godel, Echer, Bach: an eternal golden braid", Hofstadter, 1980, I have found the following in chapter X:

    "Programming in different languages is like composing pieces in different keys, particularly if you work at the keyboard. If you have learned or written pieces in many keys, each key will have its own special emotional aura. Also, certain kinds of figurations "lie in the hand" in one key but are awkward in another. So you are channeled by your choice of key. In some ways, even enharmonic keys, such as C-sharp and D-flat, are quite distinct in feeling. This shows how a notational system can play a significant role in shaping the final product."

    I think it's a nice way to say that multiple languages may help solving complex problems if each language is used to exploit its strength. It is related with CLR and the music context is appropriate to the name C#.

Style

  1. What is the etymology of Reader, Scanner and Writer? The "er" suffix suggests they are procedures but in fact they are records.
    A:
    Reader, Scanner and Writer are nouns, not verbs so, extrapolating from the naming guidelines in "Programming in Oberon" by Reiser and Wirth, they are appropriate for type names.

  2. What naming conventions can be recommended?
    A:
    Extracted from "Object-Oriented Programming in Oberon-2" by Hanspeter Mössenböck:
    Name of     Begin with         1st letter   Examples 
    ------------------------------------------------------------- 
    Constants   Noun or Adjective  Small        version, wordSize 
    Variables   Noun or Adjective  Small        full 
    Types       Noun               Capital      File, TextFrame 
    Procedures  Verb               Capital      WriteString 
    Functions   Noun or            Capital      Position 
                Adjective          Capital      Empty, Equal 
    Module      Noun               Capital      Files, TextFrames
    
    When declaring a pointer type X, the record pointed to should be called XDesc, as in this example: TYPE List = POINTER TO ListDesc; ListDesc = RECORD
    ... END;

Types

  1. The following two definitions yield two apparently different sizes:
        TYPE RecA = RECORD 
            Elements : ARRAY 25 OF SYSTEM.BYTE; 
            END; 
    
    TYPE RecB = ARRAY 25 OF SYSTEM.BYTE;
    When I look at the SIZE of RecA, I see 28, when I look at the SIZE of RecB, I see 25 (!). Thus, if I form a packet to ship with UDP, and I depend on the structure set up using RecA to have a size of 25, I am in trouble. What is being done here? This will come up again as I take a data file built using, say, Delphi or M2, and attempt to read in the same file using Oberon.
    A1:
    The size of a record is always padded at the end to a multiple of 4 bytes.

    A2: The most portable way to read files is byte by byte, converting the fields explicitly using *, DIV and MOD. Only if you need high performance with large files, should you use memory-mapped records.

    The Files module provides procedures for reading little-endian ordered values (ReadInt, ReadLInt).

  2. I cannot assign two variables that have the same type (err 113). Why?
    A:
    Two variables are compatible only if they have the same type, not the same type structure.

    Take this example of two similar but different types:

         TYPE StudentGroup = RECORD  count: LONGINT  END; 
              PotatoBag = RECORD  count: LONGINT  END; 
    
    VAR s: StudentGroup; p: PotatoBag;
    ... s := p;     (* err 113 here! *)
    Just because they have the same structure, doesn't mean that they are compatible. Would you compare students and potatos? The problem is however less obvious when anonymous types are involved, declared on the fly. In this case types are always incompatible:
         VAR  a: RECORD  count: LONGINT  END; 
              b: RECORD  count: LONGINT  END;
    
    Although these two types look the same, they are incompatible because they have different declarations. To make a and b compatible you must declare the types either with:
         VAR  a, b: RECORD  count: LONGINT  END;
    
    or with:
         TYPE MyType = RECORD  count: LONGINT  END; 
    
    VAR a: MyType; b: MyType;
    In this common case of a procedure paramenter:
         PROCEDURE P(a: ARRAY 32 OF CHAR);
    
    no type will ever have the same declaration as a, thus no variable will every be compatible to a!

    There is only one exception to this rule: open arrays. In this case, the base type of both variables must be compatible. This allows to use anonymous open arrays in procedure parameters.

  3. It appears that the compiler treats whole number constants as type INTEGER. I do not know how it treats a large whole value, say 40000. I suspect it treats it as a LONGINT. It does not appear to treat values like 125 as a byte.
    A:
    An integer constant has the smallest integer type that will accomodate it. So -128, 0 and 127 would be SHORTINT, -32768, 128 and 32767 INTEGER, and -32769 and 32768 LONGINT.

    In low-level code, to be sure of the size, use SYSTEM.VAL to cast it. SYSTEM.VAL should be used in the 2nd argument of the SYSTEM procedures GET/PUT, PORTIN/PORTOUT, GETREG/PUTREG. Alternatively, you could also write:

        SYSTEM.PUT(Adr, CHR(255)) 
        SYSTEM.PUT(Adr, 0FFX) 
        SYSTEM.PUT(Adr, SYSTEM.VAL(INTEGER, myconst))
    

Programming hints

  1. What is the longest identifier that can be used in Oberon sources? How many characters of the identifier are considered unique?
    A:
    32. 32.

  2. How can I handle 16-bit unsigned numbers in Oberon?
    A:
    Convert the number to a 32-bit LONGINT value. For example:
         VAR  x: LONGINT; y: INTEGER; 
    
    x := LONG(y) MOD 10000H
    The MOD is compiled efficiently as a logical AND operation, because the divisor is a constant power of 2.

  3. How do I cast a variable? I am looking for an equivalent of p = (Process) c; in C.
    A:
    Use a type guard - cfr. Chapter 11.2 in Programming in Oberon. Casting with SYSTEM.VAL is not a good idea, because it can break the garbage collector.

  4. How do I cast REAL variables to INTEGER very fast?
    A:
    Use the built-in assembler as explained.

  5. Is there a way to improve the speed of computation for a realtime application?
    A:
    A collection of CORDIC algorithms is under development.

  6. How do I spread random numbers as uniformly as possible? I need to fill a 13*13*13 matrix with random numbers.
    A:
    RandomNumbers.Uniform() is a first approximation. Unless you are just playing games, you should avoid it. Random numbers are full of tricks. If you like reading, have a look at the new edition of Knuth or the papers of George Marsaglia. For a general reference, see the World-Wide Web Virtual Library.

    Some common generators are found in a contribution.

  7. Are there any bitwise operators available in Oberon?
    A:
    Use the SET type and its operators. Here are the operators and their bitwise equivalents.
        +     union                 bitwise or
    *     intersection          bitwise and 
        -     difference 
        /     symmetric difference  bitwise xor 
        IN    element test          bit test 
        INCL  element insert        bit set 
        EXCL  element remove        bit clear
    
    You can use SYSTEM.VAL(SET, intexpr) to convert an expression to a set and SYSTEM.VAL(LONGINT, setexpr) to convert an expression from a set.

    It is however preferable to declare straightaway SET variables which are then conveniently used in-line. That is better than declaring LONGINT variables and then to use SYSTEM.GET/PUT to convert them from or to SET type variables. SYSTEM.VAL should also be avoided.

  8. How do I write a constant value directly to memory?
    A:
        PROCEDURE P; 
        CODE {SYSTEM.i386} 
            MOV DWORD PTR @1234H, 15 
        END P;
    000AH: C7 05 34 12 00 00 0F 00 00 00       MOV     [4660],15
    
    This is equivalent what is done, using MS Tools, with:
        mov dword ptr ds:[adr], imm    giving the code: C7 05 adr imm
    
  9. Variable initialization can be at fault.
    A:
    Take a look at this short procedure:
       PROCEDURE demo( VAR a: ARRAY OF INTEGER ); 
       VAR i: INTEGER; 
       BEGIN 
          (* i := 0; *) 
          WHILE i < LEN(a) DO a[i] := 0; INC(i) END 
       END demo;
    
    Although the initialization of "i" is missing, this procedure works on PC Oberon. On Mac Oberon it fails most times because "i" has a random value, but not because of a compiler fault.

    On PC Oberon the whole stack frame gets cleared at procedure entry. Thus missing initializations (to zero) do not attract attention.

  10. Is there a portable way to identify the caller of a procedure?
    A:
    Since ETH Oberon does not have full-featured metaprogramming facilities in all versions, there exists no portable way. However, the following works on Native Oberon and Windows Oberon.
    MODULE Temp;
    IMPORT SYSTEM, Kernel, Out;
    PROCEDURE Identify(VAR modname: ARRAY OF CHAR; VAR pc: LONGINT); 
    VAR ebp, eip: LONGINT; m: Kernel.Module; 
    BEGIN 
      SYSTEM.GETREG(SYSTEM.EBP, ebp); 
      SYSTEM.GET(ebp, ebp);  (* stack frame of caller *) 
      SYSTEM.GET(ebp+4, eip);  (* return address from caller *) 
      m := Kernel.GetMod(eip); 
      IF m # NIL THEN 
        COPY(m.name, modname); pc := eip - SYSTEM.ADR(m.code[0]) 
      ELSE 
        modname[0] := 0X; pc := MAX(LONGINT) 
      END 
    END Identify;
    PROCEDURE Test*; 
    VAR name: ARRAY 32 OF CHAR; pc: LONGINT; 
    BEGIN 
      Identify(name, pc); 
      Out.String(name); Out.String(" PC="); Out.Int(pc, 1); Out.Ln 
    END Test;
    END Temp.
    
    This gives you the calling module name and PC offset, which can be used with Compiler.Compile \f to find the calling location.

    If you just want this for debugging, consider writing a HALT statement, which also gives a stack traceback. On Native Oberon you can use the hack HALT(MAX(INTEGER)) for a halt that produces a trap and then continues running.

    Bluebottle: the following editing is required to adapt the text to Bluebottle:
    replace Kernel by AosModules
    replace Kernel.Module by AosModules.Module
    replace Kernel.GetMod by AosModules.ThisModuleByAdr

Compiler

  1. How do I locate an error in the source text?
    A:
    1. Select the error report line in the System.Log with MR+MR clicks
    2. Press F1 to mark (*) the source text viewer
    3. Activate the [Locate] Button in the System.Log menu bar

  2. Why does the \z option cause the creation of a larger code?
    A:
    It is simpler to initialize the whole local-var block: this is done by a loop writing zero to the stack. Initializing only the pointers is more complex, because only the pointers must be (individually) initialized.

    A worst case example:

    VAR x: ARRAY 64 OF RECORD ..... z: PTR .... END;
    Initialize all: 
        LOAD size 
        WHILE size > 0 
            PUSH 0 
            DEC size 
        END 
    
    Initialize pointers only: MOV EAX, 0 MOV offs0[EBP], EAX MOV offs1[EBP], EAX      ...      MOV offs63[EBP], EAX
    On the other hand, it is faster to initialize only the pointers.

  3. Is there a test set for Oberon compilers?

  4. A:
    The building of a test suite is progressing. Some tests are already available. Cfr. Project Hostess Homepage.

Built-in assembler

  1. Where do I find assembler documentation?
    A:
    Start at PC Native Oberon - Compiler. Then, get some inspiration from the nicely written and informative documentation on the assembler used to teach a course in low-level programming by Jacques Eloff.

  2. Given a type declaration:
        TYPE 
            RecDesc = RECORD 
                ch: CHAR; 
                val: LONGINT 
            END;
    
    If a procedure takes a VAR parameter, say r: RecDesc, the record fields are accessed in the following manner:
        MOV EBX, 8[EBP] 
        MOV BYTE 0[EBX], 65            ;r.ch := 'A'; 
        MOV DWORD 4[EBX], 0            ;r.val := 0;
    
    But, when a variable of the record type is declared locally inside a procedure, the fields are reversed:
        PROCEDURE A; 
            VAR r: RecType; 
            CODE {SYSTEM.i386} 
                MOV -8[EBP], 65       ;r.ch := 'A'; 
                MOV -4[EBP], 0        ;r.val := 0; 
            END A;
    
    Is there a specific reason for this?
    A:
    The fields are not reversed! Offset -8 is smaller address than -4. You may look at it under another perspective:
        LEA EAX, -8[EBP]     ; load record base address 
        MOV BYTE 0[EAX], 65   ;r.ch := 'A'; 
        MOV DWORD 4[EAX], 0   ;r.val := 0;
    
  3. The compiler's behavior is unusual when casting anything to BOOLEAN. For example, I will often input data from an I/O port, which is masked with a control word and the result is checked to see if TRUE or FALSE. On other compilers I am familiar with, if any bit is set in the low byte of the incoming word, when cast to type boolean, will result in TRUE. That is, any set bit yields a TRUE. I found that this does not seem to hold for the Oberon compiler. What I do now is, compare the byte to zero. This is not a problem, but I must watch for this.
    A:
    This behavior is consistent with Pascal, where boolean was defined as enumerated type BOOLEAN = (FALSE, TRUE), i.e. ORD(FALSE) = 0 and ORD(TRUE) = 1.

  4.     VAR b: SYSTEM.BYTE; 
        BEGIN 
            b:= 2; 
            IF b = 0 THEN 
        END;
    
    I will get a compiler error at the IF line, with a message indicative of invalid operands. It works fine if I either cast b to an integer or 0 to a byte.
    A:
    Only assignment is defined on SYSTEM.BYTE, not comparison. For 8-bit values, it is better to use a CHAR variable, so:
        VAR b: CHAR; 
        BEGIN 
           b := 2X;  (* or CHR(2) *) 
           IF b = 0X THEN 
           END 
        END
    
    To convert back to INTEGER, use ORD(b).

  5. How to call a procedure from an assembler routine? The difficulty is that the assembler allows only the use of variables and not of procedures external to the routine.
    A:
    Use a procedure variable as in this example:
    MODULE MyModule; 
    
    IMPORT SYSTEM, Out;
    VAR P: PROCEDURE (n: LONGINT);
    PROCEDURE WriteRegister32(n: LONGINT); BEGIN Out.Int(n, 0); Out.Ln END WriteRegister32;
    PROCEDURE AsmCode; CODE {SYSTEM.i386} PUSH 12345678 CALL P END AsmCode;
    PROCEDURE Test*; BEGIN AsmCode END Test;
    BEGIN P := WriteRegister32 END MyModule.
  6. How do I cast a large number of variables very fast? I have to process large picture data in quasi-realtime, converting REAL values to INTEGER. Using ENTIER for that purpose is too slow.
    A:
    Use this model procedure.

  7. How to use in-line procedures.
    A:
    An in-line procedure is a procedure that is not called, but textually inserted into the caller procedure. A detailed documentation on how to use them is included in Jacques Eloff's assembler description.

22 Aug 2002 - Copyright © 2002 ETH Zürich. All rights reserved.
E-Mail: oberon-web at inf.ethz.ch
Homepage: http://www.ethoberon.ethz.ch/