How osen code is called, and what happens to values

What a value goes through at a call, an assignment and a conversion. None of it is a fact about any one /o/* name; all of it is a fact about calling, and every rule was run.

1. The one rule everything else follows from

In OSC, a bundle has no address. A message does.

So the moment you need to name a bundle – to bind it, to route it, to send it anywhere addressed – it cannot stay a bundle. It has to become an item inside a message, and that message carries the address.

Nobody decided this. It falls out of the format, and most of what follows is a consequence rather than a convention.

/x : {1, 2} evaluates to /x : { : 1, : 2 } – a message addressed /x whose single item is the bundle.

2. Assignment wraps

NAME : VALUE gives VALUE an address. If VALUE is a bundle, it is wrapped in a message to receive one.

This is the trap. An expression that just produced a bundle element – /o/bundleitem/tobundleelem, say – has its work undone by being assigned, because assignment puts it back inside a message.

The working objects show the shape to use instead: pass the conversion straight to whatever consumes it, and never bind it. From o.print.osen:

((/bndl) /o/bundleitem/tobundleelem, /xx/outlet) /o/outlet

Not:

/elem : (/bndl) /o/bundleitem/tobundleelem,      /* wraps it again */
(/elem, /xx/outlet) /o/outlet

3. Arguments first, then the function

(1, 2) /o/add        =>  : 3

4. (), {}, <()> and <{}> are all bundles

This is the part that, missed, makes osen look like a language with unfamiliar syntax rather than what it is.

They are four ways of writing a bundle. They are NOT overloaded as language constructs – () is not “an argument list” and {} is not “a function body”. There is one kind of thing here. The forms differ on two independent axes, and the parser names both:

written parser flags evaluated
( … ) BUNDLE eagerly
{ … } BUNDLE \| LAZY not yet
<( … )> BUNDLE \| FULLYQUALIFIED eagerly
<{ … }> BUNDLE \| LAZY \| FULLYQUALIFIED not yet

From osen_parser.c, rules UnqualifiedEagerOSCBundle, UnqualifiedLazyOSCBundle, FullyQualifiedEagerOSCBundle, FullyQualifiedLazyOSCBundle.

<{1, 2}> /o/add => : 3. And {1, 2} /o/add => : [ { : 1, : 2 }, "/!/_l/APPLY" ] – the bundle sits there unevaluated with the apply still pending, which is exactly what makes {} usable as a function body without {} meaning “function body”.

4a. [ … ] is a list, and it is not a bundle

An OSC message has a data section. It may be empty, but it cannot not exist. [ … ] is that list.

So these are the same message written two ways:

/foo :          /foo : []
/foo : 1        /foo : [1]

All four print identically – /foo : and /foo : 1.

4b. Why it looks postfix

Because it is close to a transcript of what you send the machine.

(1, 2) /o/add is not “call add with arguments 1 and 2”. It is:

send a bundle and evaluate it in order – push a message with no address carrying 1, then push a message with no address carrying 2 – then send a message addressed /o/add, and with it the implicit instruction to apply the top of the stack to the remainder.

OSC bundles evaluate in order. Everything that looks like syntax is a consequence of that, plus whatever restructuring the parser must add. The notation is trying to stay one-to-one with the bytes.

Understand this first. Once it is in place the rest stops being arbitrary: assignment wraps a bundle because a bundle cannot hold an address; /o/last is item-level because it works on real OSC structure and not an abstract list; a name evaluates because a message addressed /foo sent to the machine is a lookup.

4c. The words mean specific things

Four words mean specific things, and a function’s name should be read against them:

word means
items the items of a message
elements the elements of a bundle
size the complete size of a thing, whatever it is
length how many things are in it – may equal the size or be less

4d. A bundle inside something has TWO representations

This is the piece that makes /o/bundleitem/tobundleelem make sense, and it follows directly from §1.

As a bundle element. A bundle sitting directly inside another bundle. This is what the notation produces: the parser’s lazy and eager bundle rules both emit ose_pushBundle.

As a |-typed item. A bundle sitting inside a message’s data section, laid out exactly like a blob. '|' is OSETT_BUNDLE; osekit_machine_save builds the five registers this way with ose_setTypetag_impl(dest, OSETT_BUNDLE).

Why two? Because of §1. A register has to be named – /_s is an address – and a bundle cannot hold an address. So to name a bundle you put it in a message as a | item, and the message carries the address.

/o/bundleitem/tobundleelem converts the second form back into the first, which is the only reason it exists. When a machine arrives and you want to work on its stack, you have a message /_s whose item is a bundle, and you need a bundle.

Consequences worth knowing:

5. A bare name is a lookup; a trailing colon makes it an address

Written on its own, /foo parses as a string in the data section, with a lookup command. It evaluates the binding at /foo; it does not denote the address /foo.

[/a, /b, /c] returns three not bound exceptions, one per name, suggesting /add, /abs and /lt.

To make /foo denote an address, force it into address position with a trailing colon. The data section can then be left empty or given an explicit empty list:

/foo :          a message addressed /foo, empty data
/foo : []       the same message, written out

For the text of the name rather than a lookup or an address, use a string: "/away/somewhere".

6. Where the incoming bundle is

Not /_s, which is the machine’s stack register.

A Max object’s inlets bind at /xx/in/N, N counting from 0, built by a fold over /numinlets – see the worked comment in max/lib.osen.

Inside a method defined with /o/defn, the argument names given in the declaration are bound instead:

/FullPacket : ( { ... }, ["/bndl", "/inlet"] ) /o/defn

binds /bndl and /inlet for that body.

7. Items versus elements, and why the ops split

A message holds items. A bundle holds elements. libose names the three levels in its denotational specs – \BB bundle, \ela element, \ita item, defined in libose/doc/latex_macros.tex – and the /o/* wrappers inherit whichever level the underlying function works at.

Verified examples:

Choosing an op therefore needs two questions answered, not one: what does it do, and at which level.

8. Making a C function an /o/ verb

A function a module binds – ose_pushMessage(vm_s, "/hello", …, OSETT_ALIGNEDPTR, my_hello) in its ose_main – is an instruction-level verb: it takes the machine, finds its arguments on the stack, and is called as /!/hello. Called from osen as () /!/hello, its result lands beside the argument bundle and the finaliser applies the result as a name, which fails. The idiom that makes it an ordinary function at the prompt is three instructions, the same ones osen.ose uses for /time/now:

/,/s//!/hello
/,/s//o/hello
/!/o/unwrap

/o/unwrap takes the instruction name and the name to bind, and binds under the second a wrapper that unpacks the argument bundle onto the stack, runs the instruction, and leaves one result. From then on () /o/hello answers : "Hello, World!" and (() /o/hello, "!") /o/concat/strings composes like anything else. From the command line, the three follow the load:

./o.se -f o.se.osen/osen.ose '/,/s/./o.se.hello.so' /!/load '/<</_x' \
    '/,/s//!/hello' '/,/s//o/hello' /!/o/unwrap