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:
- 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 - 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.