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:

buttonaction
leftplace the caret (the insertion point)
middleexecute the command under the pointer
rightselect 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:

  1. place the caret at the end of System.Tool with a left click;
  2. type ORP.Compile Name.Mod/s ~;
  3. 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.