The grammar of osen
The rules, precisely stated, for a reader who has met the language in the tour and wants to know exactly what is accepted.
Notation
A rule is name = form ;. Quoted characters are literal; | separates
alternatives; [ x ] is optional; { x } is zero or more; words name
other rules. Whitespace — spaces, tabs and newlines — is allowed between
any two tokens and means nothing by itself. Every example is a complete
program whose answer follows →.
Values
value = number | string | name ;
number = int32 | float ;
string = '"' { character | '\"' | '\\' | '\n' | '\t' } '"' ;
A value on its own is a message with no address, which is what the leading colon in the answer shows.
42 # → : 42
"hello" # → : "hello"
Numbers
An int32 is what C’s strtol reads with base 0: decimal, 0x…
hexadecimal, or octal with a leading 0, with an optional sign. If the
digits are followed by . the token is a float instead, read by
strtof, so .5, 1.5 and 1.5e2 are floats. Digits followed
directly by letters are an integer and then a name: 1e3 is 1 applied
to e3.
0x10 # → : 16
010 # → : 8
-5 # → : -5
1.5e2 # → : 150.0
Names and binding
name = ( "/" | letter | "_" ) { address-character } ;
binding = name ":" item ;
A name that begins with / is an OSC address, which is what a name
should be; the parser also accepts a bare word. After the first
character, any character may follow except whitespace and , : ( ) { }
[ ] # * ?. A name on its own reads the value bound to it. A colon binds
one item — a value, a bundle, or a list — to a name.
/a : 10, /a # → : 10
/x : 10, (/x, 2) /o/mul # → : 20
Binding a name again replaces its value. A binding is an element like any other, so it needs a comma before the next element; a colon with no name before it is allowed and makes an anonymous message.
: 42 # → : 42
Separators
program = [ element { "," element } [ "," ] ] ;
A comma separates the elements of a program. A newline is whitespace: it does not separate anything, so a program written across lines ends each line with a comma, and an expression may be broken across lines. A trailing comma is allowed.
Two elements side by side with no comma between them are not two elements: an element followed by a name or a bracket is a call (below), and an element followed by anything else is an error.
1 2 # → Error parsing message:
Calls
call = element ( name | bundle | deferred ) ;
Arguments come first, then the function: an element followed, without a comma, by a name applies the name to the element. The right-hand side may also be a bracket that produces a function. A number or a string on the right is an error, and a bracket on the right of a bracket is not a call — the two are left side by side.
(21, 2) /o/add # → : 23
(3) /o/abs, (4) /o/abs # → : 3 : 4
(1) 2 # → Error parsing message:
(1) (2) # → : [ ( : 1 ), 2 ]
Bundles
bundle = "(" [ element { "," element } ] ")" ;
Parentheses gather elements into a bundle that is evaluated where it stands. As the left side of a call it is the bundle of arguments.
(1, 2) # → ( : 1, : 2 )
/a : (1, 2), /a # → : ( : 1, : 2 )
An empty () is an empty bundle.
Deferred bundles
deferred = "{" [ element { "," element } ] "}" ;
qualified = "<" ( deferred | bundle ) ">" ;
closed = "%{" [ element { "," element } ] "}" ;
Curly braces keep a bundle for later: nothing inside is evaluated where it is written. The result is one value, a blob holding the bundle, and the elements inside need commas between them like anywhere else.
{1, 2}
# → : { : 1, : 2 }
Angle brackets around a deferred bundle make it a bundle element rather than a blob, still unevaluated; around parentheses they change nothing. Angle brackets around a message, or in the item position of a message, are errors with their own diagnostics.
<{1, 2}>
# → ( : 1, : 2 )
%{ … } is a deferred bundle whose free names are filled in where it is
written rather than where it runs. Inside a plain { … }, a name written
/%/x is filled in where it is written and the others are not.
/x : 7, /q : %{ /a : /x }, /x : 99, /q
# → : { /a : 7 }
Lists
list = "[" [ item { "," item } ] "]" ;
item = value | bundle | deferred | closed ;
Square brackets write several items into one message. A list inside a
list is flattened. A named message cannot be an item, so [/a : 1] is
an error. A binding takes one item; to bind several values to one name,
bind a list.
[1, 2, 3] # → : [ 1, 2, 3 ]
[1, [2, 3]] # → : [ 1, 2, 3 ]
/a : [1, 2], /a # → : [ 1, 2 ]
/a : 1, 2 # → : 2
That last program is two elements — the binding, and the value 2 —
not one message with two items.
Strings
Double quotes delimit a string; inside it \" is a quote, \\ a
backslash, \n a newline and \t a tab. Two strings side by side are
an error, since a string cannot be applied.
"a\"b" # → : "a"b"
"a\\b" # → : "a\b"
Comments
# starts a comment that runs to the end of the line, and so does
/#/, which is the instruction language’s comment.
(1, 2) /o/add # a comment # → : 3
Bundles inside messages
The parser has no literal for a bundle as a message item. Binding a
bundle to a name produces one — the bundle is stored as an item of the
message that carries the name — and the printer shows it as the bundle
itself, which is what /a : (1, 2), /a answers above.
Errors
A parse error names the line and column and shows the character it
stopped at. An unclosed bracket, a closing bracket with nothing open, a
mismatched pair, a number or string where a function was expected, and
< > around a message or in a message item each have their own
message. The answers below, and the two under Separators and Calls,
are the first line of the diagnostic; the machine prints the line and
column and the source with a caret under it as well.
(1, 2
# → Unclosed '(' on line 1, column 1.
1) /o/add # → Found an unexpected closing bracket ')':
Also accepted
A . on its own between elements is accepted and ignored.