# Oberon compiler

NOTE: This compiler is not finished yet, but it's overall design is pretty much
figured out. Not everything that is documented below works as advertised, but it
should be implemented pretty much like documented.

The unit at [comp/oberon.fs] is an Oberon Compiler. It's very close to
specifications and is able to compile source from the Oberon Project almost
unchanged.

On top of that, it also follows the design philosophy of other compilers in
Dusk: good interoperability with the Dusk system.

## Usage

You compile a Oberon unit with "oberon<<":

  oberon<< path/to/source.mod

Oberon code kind of lives in its own world. Upon loading, all procedures created
during compilation are opaque to Forth. If you want to interact with Oberon,
you need to create aliases to some of its procedures:

  obalias MyModule.MyProc myforthname

See "Calling Oberon code from Forth" below.

## Specification compliance

As mentioned above, this compiler pretty much works exactly as specified, with
minor exceptions:

* VAL is a builtin, not a SYSTEM procedure.
* SYSTEM is not really a module, but the gateway to Forth words.
* As specified, type checking is strict except for BYTE and INTEGER which are
  compatible. However, to simplify interfacing with the system, DWord, Word and
  Byte [lib/type] are also compatible.
* Specifications mention that SET width is type dependent. Ours is 32-bit.
* There is no such thing as procedure-local constants and types. Types and
  constants declared at the procedure level have the same effect as when they
  are declared outside a producedure.
* There is no such thing as a private record field. All fields are public, "*"
  in records are ignored.
* There's an extra RAWPOINTER type, see below.

## Call convention

As with other compilers in Dusk, code compiled by this compiler follows the
regular Dusk calling convention, that is, it passes arguments through PS.

The first argument is the shallowest of the stack. Return value, when there is
one, is returned on the stack. For example:

  PROCEDURE foo(x,y:INTEGER): INTEGER;

has the Forth signature "( y x -- res )" and:

  PROCEDURE foo(x,y:INTEGER);

has the Forth signature "( y x -- )".

### Open arrays are tricky

So far, so good? Yeah, there's a hitch. Open arrays, that is, "ARRAY OF
Something", need to have their length passed around at runtime and the most
straightforward way is to use an extra PS slot, right "under" the array.
Therefore:

  PROCEDURE foo(x:INTEGER; array: ARRAY OF CHAR);

has the Forth signature "( arraylen array x -- )". For this reason, it's
generally a bad idea to expose Oberon code to Forth through a procedure that has
an open array in its arguments. It's a good way to create confusing bugs.

### Pointers to structs need to be tagged

For Oberon GC [comp/oberon/gc] to work, pointers to record need to be tagged
with a link to their type. That is, all POINTER type must point to a type link,
followed by the structure.

This means, essentially, that data that POINTER points to must have been
initialized with NEW().

This means that most of the time, you won't be able to pass structs from the
Forth world into Oberon. But for this there is RAWPOINTER.

### RAWPOINTER

RAWPOINTER has the same semantics as POINTER but it can point to anything. This
allows interoperability with the Forth world.

The drawback of RAWPOINTERS is that "IS" and typeguards can't be used on them.

## Calling Forth code from Oberon

Forth words can be called through the SYSTEM module. Lookups for the SYSTEM
module are made in the sysdict through "findannotated" [comp/sig]. If found, we
compile a call to it.

## Calling Oberon code from Forth

Oberon compilation doesn't generate any Forth word. All those artifacts live
in the "Oberon world". You can hook into that world with "obalias", which has
semantics similar to "alias" except that its first argument is a fully qualified
(that is, "Module.ident") name. Example:

  obalias MyModule.MyProc myforthname

Then, you can call "myforthname" using the call convention described above.

## Sharing types between Oberon and Forth

Type lookup generally works as specified, that is, types belong to modules and
have to be fully qualified to be referred to.

But there's more. Unqualified type lookups first look into the current module,
as expected, but if that lookup fails, we look in the whole sysdict through
"findtype" [lib/type].

This allows you, for example, to share structures [lib/struct] between Forth and
Oberon without the need for duplicating the declaration.

## IMPORT autoloading

The IMPORT line goes through the specified list of module names, checking
whether each module is loaded in memory (exists in the "modules" dictionary).
If it doesn't, run the equivalent of "oberon<< oberon/<modulename>.mod".

The name "SYSTEM" is ignored and has no effect.

## Injecting a Forth word into a module

Sometimes, you want to "fake" a word in an Oberon module and actually implement
that word in Forth. You can do so with ":obproc":

  :obproc ModuleName.ProcName(arg1:INTEGER; arg2: BYTE): INTEGER;
    \ Forth signature: ( arg2 arg1 -- res )
    swap - ;

This procedure will be added to ModuleName as a public procedure and will behave
just as if it had been written in Oberon.

Note that if you inject a word in a module from the Oberon system, you should
either be certain that this module is already loaded before you inject, or make
sure that you load the module after you're done injecting, because as soon as
an injection takes place, the IMPORT autoloading mechanism won't load the
module.

## API

oberon<< ( "path" -- )
  Compile the Oberon module at "path".

obalias ( "qualident" "name" -- )
  Look for procedure "qualident" (modulename.name) in the Oberon world and
  export it as a Forth word named "name".

obtype ( "qualident" "name" -- )
  Look for type "qualident" (modulename.name) in the Oberon world and
  export it using "addtype" [lib/type].

:obproc
  See "Injecting a Forth word into a module" above.
