Registers, and how namespacing works

A register is a bundle inside the machine’s bundle, with a name. The stack, /_s, the environment, /_e, and the host register, /_x, where the host binds what it can do, are registers, and the overview lists the rest. Hosts and modules make their own: /le and /lc are the line editor’s, /py o.se.python’s.

They are not a special kind of object. A register is an ordinary OSC bundle, in the same buffer as everything else, reachable as bytes like everything else. What makes it a register is that it has a name and the machine knows how to find it.

The name is three characters

A slash and two more. /_x, /le, /py. Not four, not two. A name whose second character is _ is the machine’s own — /_s, /_e, /_x — so a host or a module picks two other characters.

(65536, "/ab") /o/make/register     # fine
(65536, "/audio") /o/make/register  # refused: errno, nothing created

That is a hard rule, checked when the register is made.

Namespacing: the first segment selects the register

An address whose first three characters name an existing register is looked up inside that register, with the prefix stripped:

/py/udp/sendto   ->  register /py,  address /udp/sendto
/_e/x            ->  register /_e,  address /x
/udp/sendto      ->  no register named /udp, so: an ordinary address

This is OSC’s own mechanism rather than an invention. An OSC address is a path; the first segment says where the rest is resolved. Nothing is parsed and nothing is allocated to do it — a register name is a fixed three characters, so the split is a comparison and a pointer addition, which is why it is cheap enough to do on every lookup on a microcontroller.

It works for reading and for binding:

(65536, "/ab") /o/make/register,
/ab/thing : 42,            # binds /thing INSIDE /ab -- /_e does not grow
(/ab/thing) /o/println     # : 42

/_e is a register like any other, and its own level is the session. While a function runs, /_e also holds the scopes of the calls in progress (bundles marked as scopes, after the session’s bindings). A plain /x : v binds in the call it is in, and goes when the call returns. /_e/x : v names the register, so it does what /ab/x : v does for /ab: it replaces the session’s /x, from wherever it is written, and lasts. /_e/x reads the session’s /x. A bare /x finds the nearest: the call’s own, then its callers’, then the session’s.

The search order, in detail

Every place a name is looked for, in order; the first that has it answers. For an address /xy/rest:

  1. the register /xy, if one exists, for /rest
  2. the register /xy of each machine this one is chained to, outward, for /rest
  3. the environment /_e, for the whole address /xy/rest
  4. the host register, /_x, for the whole address
  5. a read-only vocabulary, if the host installed one, for the whole address

And when all five miss, an inner machine does not stop there: its unbound-name handler searches its whole outer machine. That is how a line typed at the prompt, which runs in an inner machine, finds /o/add.

Step 2 is what a register-qualified name means: /_l/APPLY asks for APPLY in a /_l – this machine’s, or its outer’s, and so on out – not in this machine’s /_l and then in everything else it has. An inner machine that borrows its language has no /_l or /os of its own, and finds its outer’s this way.

Two things follow from the order.

Steps 1 and 2 before step 3 are shadowing. A register searched first can replace a name without anything else being told, which is how a module can take over a binding another module made.

Step 3 after a miss in steps 1 and 2 is the fallback, and it is what makes the convention additive instead of a trap. Without it, creating a register named /ab would make every existing /ab/... binding report not bound — action at a distance from a collision nobody chose.

A prefix that names no register is not an error: the address is simply an address. That is what allows /o/println to exist without anybody having to avoid the letter o.

Within the host register, the newest binding of a name answers. A host or a module pushes bindings onto the end of the host register and never deletes them, which is what lets a later one shadow an earlier one. A binding made through the register – /_x/name : ... – replaces the entry rather than adding a second.

The host register belongs to the host

The convention: /_x is the host’s. A module or a program that wants a register makes its own. Nothing enforces it and nothing can – see below – but it is what every host and module does.

Two things are worth knowing alongside it, because a stricter version of the rule would be wrong.

A computed definition lands in the host register and reads back as an ordinary name. The two lines below put /zzz in different places:

/zzz : 42                              # binds /zzz -- in /_e
("/_x", 42, "/zzz") /o/assign/toreg    # binds /zzz -- in /_x

/_e/zzz answers 42 after the first and /_x/zzz reports not bound; after the second, /_x/zzz answers 42. A bare /zzz reads back either way – the first through step 3, the second through step 4 – so the two are indistinguishable at the point of use even though they are stored in different registers.

That is what makes the second line a definition rather than a reach into the host’s private state, and it is the only way to define a name whose address is computed rather than typed. o.pack.osen builds its /assist/... bindings that way because it cannot know the addresses until it has read its box arguments.

And the order between them is the thing to notice: /_e is searched before /_x, so a name a user binds shadows a host or standard-library name of the same address. /o/add : 999 makes /o/add answer 999 while the real one sits untouched in /_x, reachable as /_x/o/add. That is deliberate: a binding you make wins.

So the rule cannot be enforced by making the register unavailable, and it cannot be enforced by inspection either: nothing can tell a computed definition from a scratch variable by looking at it. It is a convention, and the reason to keep it is that /_x holds the whole /o/* vocabulary alongside whatever you put there. A name of your own in a register of your own cannot collide with the standard library; a name in /_x is one unlucky choice away from replacing part of it.

/o belongs to osen

/o/... is the osen standard library’s namespace: /o/add, /o/println, /o/audio/create. Host C never binds there. A host’s own names go under its own prefix, and osen wraps the ones a program should see: this host’s C binds /udp/send, and /o/udp/send is written in osen, in the file osen keeps for this host (osen.ose; the page’s is web.ose, and there is one per host under host/). So an /o/ name is always osen’s, even when what it wraps is the host’s.

The same holds for a module: o.se.python binds /py/..., not /o/..., and a Python file loaded into it is reachable as /py/udp/sendto rather than colliding with whatever osen calls that. A program of its own may bind under /o, and then it is overriding part of the language, which is occasionally what you want and usually not. Override /o deliberately or not at all.

Making one

(65536, "/py") /o/make/register

Size in bytes first, then the name. The size must be a multiple of four, at least a register’s own overhead, and no larger than what the machine has left; all three are checked, and a violation sets errno and creates nothing.

And the name must be free. A second claim on a live register is refused – “a register by that name already exists” – and creates nothing. Two modules that both want /le is a genuine conflict under any naming scheme, so it is refused rather than resolved.

Implementation — for embedders and module authors

Implementation

Which layer owns a name

/_i, /_s, /_e, /_c, /_d, /_x, /_p look like a family, and /captured, /ambient, /name, /body, /args look like another. That is not decoration and it is not inconsistency — it says which layer owns the name, and you can read it off the shape:

shape owned by example
/ + two characters libose — it is in osevm_decl_registers[], and osevm_declares() reports it /_i, /_p
anything longer one language — a slot that language keeps, invisible below it /captured, /ambient

/_p is register-shaped although no register is ever made for it; ose_vm.c says so in as many words: ”/_p is last because it is the one libose defines and never creates.” It is bit 5 of the same enum as the five machine registers, so every client of libose shares the name whether or not it has a language. /captured is nine characters because no client below osen has any use for it — osen_lib.h: “it has no register to become: libose’s /_* names are REGISTER names and OSEVM_DECL_* is a bitmask over registers, and this is neither.”

A value can carry both at once, and then the shape tells you where to go look:

({ … }, [ "/x" ], /ambient : [ "/y" ]) /o/defn
    ->  /_i   /ambient   /_p

/_i and /_p are libose’s and osevm_declares() reports them; /ambient is osen’s and libose cannot see it. (/captured is not in that list because it does not survive into the value at all — it becomes /_e.)

Searching the host register

Every search of /_x goes through osekit_x_offsetForMatch(), which asks the index where the host installed one and otherwise walks the whole register and keeps the last match, so a name means the same on every host. An inner machine’s /chain/outer and /unbound are one of each, at the front of its /_x, and chaining again replaces them.

One implementation

ose_addressToRegister() in libose/ose_context.c does the split for every lookup and every binding, and OSE_REGISTER_ADDRESS_LEN is the length.

From C

ose_pushContextMessage(osevm, size, "/py") makes a register, and returns the new message’s offset or -1 if it refused. The size must be at least OSE_CONTEXT_MESSAGE_OVERHEAD; a name already taken is refused with OSE_ERR_REGISTER_EXISTS.