# Types

The unit at [lib/type.fs] provides a universal type system. The main goal of
this unit is for different programming languages to talk to each other, but the
type system can be used independently.

What are types for? For answering these questions:

1. What kind of value do I have here? integer, pointer, array or struct...
   something else?
2. If it's an integer, is it 8, 16 or 32-bit in width? signed?
3. If it's a pointer, what type does it point to?
4. If it's an array, what is it an array of? How big?
5. etc..

## The Type structure

What we call a Type is a structure with a Category as its first field followed
by a structure that is specific to that category.

A Category is an opaque structure that contains its name and base handling
routines. Its address is its ID. To check whether a type if of a particular
category, we compare addresses.

New categories can be added with ":category". For example, [comp/oberon] has
this concept of sets that is unique to it. These sets have their own category.
Other languages having no notion of Oberon sets will choke when they encounter
one of these categories, but this allows Oberon to operate within global types
semantics while having its own specificities.

There are builtin categories:

Untyped
Integer
Pointer
Array

Structures are defined in [lib/struct].
Signatures are defined in [comp/sig].

## Type entries

When you create type entries in the sysdict, you can use "addtype" instead of
"create" or "const". It will make the entry tagged [doc/usage/tag] with "TYPE".

Then, what you can do is to use "findtype" which takes uses "findtagged" to give
you a universal type repository.

## All types have a size

All types have a size, yielded by "typesz". This number represents the number
of bytes that a single element of that type needs for storage. This size is
*not* aligned. There are moment when you'll want to apply alignment to that
size.

## All types are printable

All types can be printed through ".type ( type -- )". When you define a new
category, you have to supply it with a "printer word". This makes types very
introspectible.

## All types are are HAL-accessible

All types have a routine that fiddles with a base HAL [doc/hal] operand, for
example the ones coming from [comp/sym] ("W)", "RSP) 4 +)", etc.), then apply
the proper HAL modifiers on it. For example, a Integer(2) would apply "16b)",
a structure or array would apply "&)".

An important aspect to keep in mind is that when a type needs to apply 32b), it
needs to actually be done (as opposed to other context where 32b is the default,
so we don't need to apply it). The input HAL operand, in compiling context, will
often come from an Array or Structure and thus have a &) applied to it. If we're
then looking, for example, to build a HAL access to a structure field, then it's
important that this 32b) is explicitly applied, or else the &) will stick around
when it shouldn't.

These modifiers are applied with "type) ( halop type -- halop )"

## Untyped category

This category is for unknown types. It has a 0 size. type) raises an error.

## Integer category

Types of this category represent a straight integer number.

+00 Category
+04 Width 1=8b 2=16b 4=32b anything else is invalid
+08 Signed?

type) applies 8b) 16b) or 32b) depending on size.

## Pointer category

Types of this category represent a value that points to an address where
there's another value of the specified type.

+00 Category
+04 Type being pointed to

type) applies 32b)

## Array category

Types of this category represent an address where there's a specific number of
elements of a specific type.

+00 Category
+04 Type of the elements
+08 Count

There is no convention for representing an array of an unknown Count. It's
better to do like in C and represent such values as "pointer to XXX", with the
understanding that more "XXX" are following or preceding the address we point
to.

type) applies &).

## Forward types

Sometimes, types reference each other, for example in self-referencing
structures. This unit handles this use case by giving a special meaning to types
of the Untyped category and by providing "?addtype".

When you encounter a situation where you want to add a forward reference, that
is, probably when you ran "findtype" and it yielded 0, you run "forwardtype
addtype" and carry on with whatever you were doing.

Then, later, when you add a type that might have been targeted by a forward
reference, that is, probably a structure, you run "?addtype" instead of
"addtype".

This word first performs a lookup of the name of the type to add. If it exists
*and* if this type is of the Untyped category, then instead of adding a new
type, we copy the contents of the type we were about to add to the space
allocated by the forward reference.

Note that "findtype" is a tagged search, so a non-type word being defined with
the same name as the type we want to add will not be considered by the logic
above.

## Example usage

  0 4 integertype addtype uint
  1 1 integertype addtype char
  uint pointertype addtype uint*

  uint* reftype typesz . \ prints 4

## API

### Constants

MAXTYPESZ  4. Maximum size in 32-bit words that a type can possibly have.

### Builtin types

Void   Untyped
DWord  Integer(4)
Word   Integer(2)
Byte   Integer(1)
String Pointer(Integer(1))
AnyPtr Pointer(Void)

### Words

:category ( "name" ... -- )
  Create a new category along with its "typesz" then ".type" then "type)"
  routines. Semantics are like ":", but you do it thrice! Example:

  :category Integer @ ; @+ . @ if 'S' emit then ;
    typesz case 1 = of 8b) endof 2 = of 16b) endof 4 = of 32b) endof
      halerr endcase ;

  To save ourselves a tedious and repetitive "4+" in those routines, the
  signature of those handlers isn't "( type -- ... )", but "( type+4 -- ... )",
  that is, we skip the Category field.

typesz ( -- n )
  Yields the size in bytes needed to store an element of that type.

.type ( type -- )
  Print a human-readable representation of type.

type) ( halop type -- halop )
  Apply appropriate HAL modifiers to "halop" so that this type is properly
  accessed.

addtype ( type "name" -- )
  Same as "const", but tags the entry with "TYPE"

?addtype ( type "name" -- type entry )
  Perform a lookup for "name". If found and if of the Untyped category, rewire
  the found entry to "type". Otherwise, run "addtype".

  In both cases, yields the resulting type (either the one added or forwarded)
  and the resulting entry (either added or forwarded).

  See "Forward types" above.

findtype ( name -- type-or-0 )
  Uses "findtagged" to perform a dict lookup in sysdict specifically for entries
  with the "TYPE" tag.

category ( type -- category
  Yield the category of a type.

Integer ( -- category )
Pointer ( -- category )
Array ( -- category )
  Yields the category struct of the builtin types.

untyped ( -- type )
integertype ( signed? width -- type )
pointertype ( type -- type )
arraytype ( count type -- type )
  Create a new type of the specified category with the specified fields.

forwardtype ( -- type )
  Like "untyped", but with extra allocated space afterwards.

  See "Forward types" above.

intsigned? ( inttype -- f )
  Yields whether inttype is signed.

reftype ( type -- type )
  Works for both Pointer and Array (and when you create a new category, if
  there's a reference Type in it, us the same offset so that you can reuse
  "reftype"). Yields the type it refers to.

arraycount ( arraytype -- count )
  Yields the count of a type of an Array category

typealign ( a type -- a )
  Align "a" to an address that properly stores "type", that is, align to 2 for
  typesz=2, don't align for typesz=1 and otherwise, align to 4.
