# Oberon/ETH Oberon/faqlang

This document was originally hosted at the ETHZ. It remains under the ETH license and is in the WayBack archive.
Note: Questions not specifically related with the Oberon language or the compiler are dealt with in the FAQ on the ETH Oberon system.
General
Style
* What is the etymology of Reader, Scanner and Writer?
* What naming conventions can be recommended?
Types
* What is the size of the record?
* Why can't I assign a variable to another one of the same type?
* How does the compiler treat whole number constants?
Programming hints
* What is the longest identifier that can be used in Oberon sources?
* How can I handle 16-bit unsigned numbers in Oberon?
* How do I cast a variable?
* How do I cast REAL variables to INTEGER very fast? ( quasi-realtime)
* Is there a way to improve the speed of computation for a realtime application?
* How do I spread random numbers as uniformly as possible?
* Are there any bitwise operators available in Oberon?
* How do I write a constant value directly to an memory?
* Variable initialization can be at fault
* Is there a portable way to identify the caller of a procedure?
Compiler
* How do I locate an error in the source text?
* Why does the \z option cause the creation of a larger code?
* Is there a test set for Oberon compilers?
Built-in assembler
* Where do I find assembler documentation?
* What is the order of fields in a record?
* The compiler's behavior is unusual when casting anything to BOOLEAN.
* What is the correct syntax for operating on SYSTEM.BYTE?
* How to call a procedure from an assembler routine?
* How do I cast a large number of variables very fast?
* How to use in-line procedures
* 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:
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#.
* 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: SYSTEM.PUT(Adr, CHR(255)) SYSTEM.PUT(Adr, 0FFX) SYSTEM.PUT(Adr, SYSTEM.VAL(INTEGER, myconst))
* What is the longest identifier that can be used in Oberon sources? How many characters of the identifier are considered unique? A: 32. 32.
* 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.
* 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.
* 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.
* 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.
* 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
* 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.
* 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
* How do I locate an error in the source text? A:
** Select the error report line in the System.Log with MR+MR clicks
** Press F1 to mark (*) the source text viewer
** Activate the [Locate] Button in the System.Log menu bar
* 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.
* 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.
* 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.
* 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;
* 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.
* 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 To convert back to INTEGER, use ORD(b).
* 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.
* 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.
* 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/