Registers, and how namespacing works

A register is a bundle inside the machine’s bundle, with a name. /_s is the stack, /_e the environment, /_x the host register, where the host binds what it can do. Hosts and modules make their own: /le and /lc are the line editor’s, /db the database’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

The search order

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

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.

Both of the last two matter.

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 and never has been: 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, because nothing else in this file says it: /_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. A host or module that binds under /o is overriding part of the language, which is occasionally what you want and usually not.

So: put your names under your own prefix. 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. 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.