The system: text instead of buttons
The least familiar thing about Oberon is neither the language nor the processor but the interface. There are no buttons, menus, dialogues or icons. There is text, and any text can be executed.
This chapter is worth reading before the first lab; without it the system looks broken.
The middle button executes
The basic rule: a middle click on a word of the form Module.Procedure runs
that procedure. Not a button, not a menu entry — any such word, wherever it
happens to be written: in the command list, in a letter, in a comment, in text
you typed yourself a moment ago.
On a mouse without a middle button, Alt with the left one stands in for it.
From this follows the thing that throws people at first: the system draws no
distinction between a "document" and a "toolbar". The lower right area with the
text System.Open, ORP.Compile, Tools.Inspect is an ordinary text file
called System.Tool. You can edit it, add your own commands to it and save it.
What you typed yourself works exactly as what came with the system.
Three buttons
Each mouse button does its own thing:
| button | action |
|---|---|
| left | place the caret (the insertion point) |
| middle | execute the command under the pointer |
| right | select text by dragging |
The selection and the caret are different things and exist at the same time. This matters: many commands take their argument from the selection and put the result where the caret stands.
The arrow and the tilde
Two hint marks appear in the command list.
An arrow ↑ after a command name means the argument comes from the current
selection. System.Directory ↑ — select a pattern such as *.Mod, then click
the command.
A tilde ~ closes the parameter list. ORP.Compile Math.Mod/s ~ — command,
file name, option, tilde. Without the tilde the command reads too far and will
most likely complain.
Windows
The screen is divided into vertical tracks, each holding a stack of windows. Every window has its own black title strip, and in it the commands that concern that window:
System.Log | Edit.Locate Edit.Search System.Copy System.Grow System.Clear
System.Close closes, System.Grow enlarges, Edit.Store saves. It is the
same mechanism: the words in the strip are ordinary text, executed with a middle
click.
Two windows are open right after booting. Above is System.Log, where all messages go: compilation results, errors, reports from commands. Below is System.Tool, the command list.
The log has a quirk worth knowing in advance: it shows text from the beginning
and does not scroll itself. If the output runs longer than about eighteen
lines, the tail is simply not visible even though the command finished.
System.Clear in its title strip empties the log — use it between
experiments.
The ordinary way of working
To compile a module:
- place the caret at the end of
System.Toolwith a left click; - type
ORP.Compile Name.Mod/s ~; - run it with a middle click on what you typed.
A line like this appears in the log:
compiling Math 447 0 32C32F12
That is the module name, the code size in words, the data size in bytes and the interface key. The key is covered in the chapter on modules.
The /s option permits overwriting the symbol file. Without it the compiler
refuses to change the interface of a module something else depends on.
To open a file for editing: Edit.Open Name.Mod; to save it: Edit.Store in
the window's title strip.
One thing that surprises
The system is single-tasking in the sense that a command runs to completion and the interface does not respond meanwhile. But the garbage collector runs between commands — and only between them.
You will meet the practical consequence straight away: ask the compiler to build several large modules in one command and the symbol tables accumulate until the heap runs out. You get
pos 6734 TRAP 4 in ORB at 0001EC10
That is not a broken compiler and not a mistake in your code: trap 4 is a NIL
dereference, and NIL is what the allocation returned. The same modules, built
one per command, go through without a complaint.
We walked into this twice: first building the compiler, then rebuilding the whole system. The rule is simple — one large module per command.
Tasks instead of threads
Why between commands? Because the system has neither threads nor timer
interrupts — there is one loop, Oberon.Loop. It reads the mouse
and keyboard and, when there is no input, calls the tasks in a
circle: procedures put into the circle with Oberon.Install. A task
must hand control back quickly — nothing can take it away. While a command or
a task runs, nothing else happens: the mouse pointer does not move, the caret
does not blink, other tasks do not run. An endless loop in a task kills the
whole system. You will do all of this yourself in lab 11.
The garbage collector is a task too. The last lines of
Oberon.Mod:
ActCnt := 0; CurTask := NewTask(GC, 1000); Install(CurTask);
Once a second the loop calls GC, but it sweeps only for one of
two reasons: BasicCycle = 20 human actions (keystrokes and clicks)
have passed since the last sweep, or less than 64 KB is left in the heap.
System.Collect sweeps nothing itself — it only zeroes the action
counter.
And the main point: the collector marks from the modules'
global pointers (Kernel.Mark(mod.ptr)) and never
scans the stack. In the middle of a command live objects are held by its local
variables, and a sweep would throw them away. Hence only between commands,
when the stack is empty. You will watch the heap before and after a sweep,
through the variable Kernel.allocated, in lab 10.