|
|||||||||||
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
Types
Programming hints
Compiler
Built-in assembler
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#.
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;
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).
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.
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))
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.
Some common generators are found in a contribution.
+ 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.
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
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.
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
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.
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;
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).
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.
22 Aug 2002 - Copyright © 2002 ETH Zürich. All rights reserved.
E-Mail: oberon-web at inf.ethz.ch
Homepage: http://www.oberon.ethz.ch/
Wichtiger Hinweis:
Diese Website wird in älteren Versionen von Netscape ohne
graphische Elemente dargestellt. Die Funktionalität der
Website ist aber trotzdem gewährleistet. Wenn Sie diese
Website regelmässig benutzen, empfehlen wir Ihnen, auf
Ihrem Computer einen aktuellen Browser zu installieren. Weitere
Informationen finden Sie auf
folgender
Seite.
Important Note:
The content in this site is accessible to any browser or
Internet device, however, some graphics will display correctly
only in the newer versions of Netscape. To get the most out of
our site we suggest you upgrade to a newer browser.
More
information