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:
- the register
/xy, if one exists, for/rest - the register
/xyof each machine this one is chained to, outward, for/rest - the environment
/_e, for the whole address/xy/rest - 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.