Oberon/ETH Oberon/faqlang

 

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 creation 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 A''': The following was reported by Antonio Cisternino:
 * 1) '''What advantage do you see in using Oberon rather than other widespread programming languages?
 * 1) '''What advantage do you see in using Oberon rather than other widespread programming languages?

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 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. '''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
 * 1) '''What is the etymology of Reader, Scanner and Writer? The "er" suffix suggests they are procedures but in fact they are records.

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  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).

'''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 ever 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.

'''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: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> SYSTEM.PUT(Adr, CHR(255)) SYSTEM.PUT(Adr, 0FFX) SYSTEM.PUT(Adr, SYSTEM.VAL(INTEGER, myconst)) </li></ol>

Programming hints <ol> '''What is the longest identifier that can be used in Oberon sources? How many characters of the identifier are considered unique? A''': 32. 32.</li>

'''How can I handle 16-bit unsigned numbers in Oberon? A''': Convert the number to a 32-bit LONGINT value. For example: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> 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.</li>

'''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.

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

<li id="CORDIC">'''Is there a way to improve the speed of computation for a realtime application? A''': A collection of CORDIC algorithms is under development.

'''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 Wikipedia.

Some common generators are found in a contribution.</li>

'''Are there any bitwise operators available in Oberon? A''': Use the SET type and its operators. Here are the operators and their bitwise equivalents. <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> +    union                 bitwise or -     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.
 * intersection         bitwise and

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.</li>

'''How do I write a constant value directly to memory? A''': <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> 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: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> mov dword ptr ds:[adr], imm   giving the code: C7 05 adr imm

'''Variable initialization can be at fault. A''': Take a look at this short procedure: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> 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.</li>

'''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. <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> 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</li></ol>

Compiler <ol> '''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 </li>

'''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: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> VAR x: ARRAY 64 OF RECORD ..... z: PTR .... END; Initialize all: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> LOAD size WHILE size > 0 PUSH 0 DEC size END Initialize pointers only: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> 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.</li>

'''Is there a test set for Oberon compilers? A''':The building of a test suite is progressing. Some tests are already available. Cfr. Project Hostess Homepage.</li></ol>

Built-in assembler <ol> '''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.</li>

Given a type declaration: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> 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: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> 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: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> 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: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> LEA EAX, -8[EBP]     ;load record base address MOV BYTE 0[EAX], 65  ;r.ch := 'A'; MOV DWORD 4[EAX], 0  ;r.val := 0; </li>

'''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.</li>

<li id="BYTE"><pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> 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: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> VAR b: CHAR; BEGIN b := 2X; (* or CHR(2) *) IF b = 0X THEN END To convert back to INTEGER, use ORD(b).</li>

'''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: <pre style="font-family: monospace, monospace; font-size: small; line-height:1.25; font-weight: normal; background-color: white; border: none; padding: 0; overflow: hidden; margin-left: 2em; "> 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. </li>

'''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.</li>

<li id="in-line">'''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.</li></ol>

[Top] ''22 Aug 2002 - Copyright &#169; 2002 ETH Z&#252;rich. All rights reserved.''

E-Mail: oberon-web at inf.ethz.ch

Homepage: http://www.ethoberon.ethz.ch/