# Units and dependencies

In Dusk, we call a "unit" a collection of words tied to a certain name (the unit
name). For example, the unit "lib/str" provides the word "s)". Its structure is
described in [doc/data].

In their primary use case units are tied to a source file of the same name. For
example, the source file associated with unit "lib/str" is lib/str.fs. Its
associated documentation lives at doc/lib/str.txt.

A unit is declared with the word "unit". For example, "unit lib/foo" declares
the unit "lib/foo". All following dictionary entries following this declaration
will be associated with the unit "lib/foo". This association stops when the word
"endunit" is called. Following words will be unassociated.

Units can't be nested and the word "unit" implicitly calls "endunit".

Declaring a unit creates a word of the same name. That word can be called
directly, but that's not it's primary use case. It's primary use case is the use
of the unit name with "needs". It "bubbles" its associated words on top of the
system dictionary, that is, it makes those words be found before all the others.

This simple little trick allows complex dependency chains to occur while
avoiding word shadowing problems that inevitably crop up as a Forth codebase
becomes wider.

For example, let's say that you loaded "lib/str" earlier, but then created a new
word called "s)" which has completely different semantics from lib/str's "s)".

So far, so good. You can do that and everything will work fine. However, now you
want to load "app/foobar" which depends on "lib/str" and it just so happens that
it needs to use "s)". Guess what? it's going to pick up the wrong "s)" and
"app/foobar" is going to be broken.

But if "app/foobar" does "needs lib/str", then all those words are going to be
bubbled up to the top in the system dictionary. The words don't actually move,
only the dictionary chain links change (so all compiled references to those
words are still valid, of course). But because "app/foobar" declared that need,
the the "s)" it actually needs will re-shadow the "s)" that shadowed it earlier.

Let's see an example where "lib/a" define a single word, "a", "lib/b", "b" and
"lib/c", "c":

  f<< lib/a.fs
  f<< lib/b.fs
  f<< lib/c.fs
  words \ prints "c b a (... rest of system dict)"
  needs lib/a
  words \ prints "a c b ..."
  needs lib/c
  needs lib/b
  words \ prints "b c a ..."

## The "needs" word

Let's talk more about this "needs" word.

When called, "needs" reads the rest of the line and then expects a list of unit
names separated by whitespace. The last element of the line must be directly
followed by a LF (no whitespace in between).

As long as [fs/core] is not loaded, "needs ..." is the equivalent to calling
each of the following words directly. Once [fs/core] is initialized, it checks
whether the name exists in the system dict and automatically loads the unit from
file if it doesn't. The associated file is "<unitname>.fs". For example, doing
"needs lib/str" when "lib/str" doesn't exist in the system dictionary would
do "f<< lib/str.fs" first, then call "lib/str".

"needs" cannot be called inside a unit definition. Like "unit", it implicitly
calls "endunit".

Sometimes, the "needs" list can be quite lengthy and span over multiple lines.
Rather than calling "needs" more than once, which is incorrect, you can insert
a "\" at the end of a line to indicate that the unit list continues on the
following line.

  needs a \
    b \
    c

is exactly equivalent to:

  needs a b c

## The two stages of needs

Naively, you could think that:

  needs lib/a lib/b lib/c

and:

  needs lib/a
  needs lib/b
  needs lib/c

are equivalent. You'd *almost* be right. If all libraries are already loaded in
memory, they are equivalent. If one of the libraries is not, then the two forms
are not equivalent because the "bubbling up" that is a side effect of the
loading will mess up our own top level "bubbling up".

For example, if "lib/c" wasn't already in memory and it depended on "lib/a",
then "lib/a" is going to end up "on top" of "lib/b", which is not the intent.

It sets the stage for some really tricky bugs and we don't want that. We want a
given namespace following a "needs" to be deterministic.

Therefore, needs operates in two stages. First, it goes through the specified
units and ensure that they're loaded in memory. Then, it goes through the list
again, from left to right, then "bubbles up" each unit one after the other.

This ensures a deterministic namespace, independent of the prior "loaded" state
of the specified dependencies.

## On-the-fly unit sub-lookups

In Dusk's code, we avoid both namespaces and word name prefixes. Each unit
chooses its names for "maximum local elegance", that is, the code should be
understandable locally, without adding spurious prefixes to avoid global name
clashes.

In practice, we find that as long as "needs" list are thoughtfully ordered, such
clashes rarely occur.

... but it *can* happen. When that happens, you have one last wildcard: calling
the unit word directly.

The unit word is a "find selector" (see [doc/usage/word]). When called, it
consumes the following word, then begins a regular dictionary lookup, but
starting from the unit's top entry rather than from the system dictionary.

This means that if the looked up word has been shadowed since the declaration of
the unit, this lookup will still find the word from the unit.

That word, being a find selector, works just as fine in interpret and compile
mode.

Let's imagine that both "lib/foo" and "lib/bar" have a "xlerb" word. As long as
they're not used together, no problem. They could even be used together, but
only one of the "xlerb" is used, so it's only a matter of correctly ordering the
"needs".

But if we need to use them both, you'll need to so something like:

  needs lib/foo lib/bar
  : callbothxlerbs lib/foo xlerb lib/bar xlerb ;

or, if you want to rely on your "needs order a bit, you can be more terse:

  needs lib/foo lib/bar
  : callbothxlerbs lib/foo xlerb xlerb ;

The second "xlerb" comes from lib/bar because it's the top unit of the needs
list.

## xcomp/boot unit

There is a builtin unit called "xcomp/boot". It includes all kernel words as
well as the words defined in [xcomp/boot.fs]. This includes the HAL.

You usually don't need to do include it in your needs list because most code
avoid shadowing core words. But in some case, it's necessary.

But the general rule if you want to have a good time is: don't shadow xcomp/boot
words.
