MESSo
This is the draft specification for MESSo, or message oriented programming language. My goal with it was to create a non-Turing-tarpit language that's still weird enough to be called esoteric. If that goal hasn't been met, you're welcome to make changes to the specs or just bury the whole thing in an unmarked grave during the next full moon.
MESSo is very much a work in progress, and I invite anyone with any ideas to go ahead and implement them in the specs. I'm not going to go all Linux on the specs and watch every change that gets made; I want this to be a "true" community project.
Syntax and data types
MESSo's syntax is slightly C-ish, but since the fundamental concepts are wildly different, most things like variable assignment just don't quite work like you'd expect them to. I don't grok BNF so you'll just have to bear with me here.
Functions are called in a similar fashion to C, using name(parameters). Code blocks are delimited with []s (compare with {} used in C), and expressions must be terminated with a ; in a fashion similar to C and its ilk.
Strings
Strings are simply text delimited with ""s, and they can contain the already-familiar C-esque escape characters in the \n format.
Numbers
There is only one data type for numbers, which can be either an integer or a floating-point number.
Maximum precision of the number data type
This space intentionally left blank.
Lists
Lists are defined with {value, value , value...} where value can be either a number, string or another list. To access a specific element of a list, use the name<index> syntax, i.e.
foo<0>
would return the 1st element,
foo<1><0>
would return the 1st element of the list that is the 2nd element in foo.
Associative lists
Similar to the associative array used in Perl. Defined with {key:value, key:value...} where key is any alphanumeric string and value can be either a number, string, list or associative list. Accessing elements is done using the same syntax as with ordinary lists, the index is just replaced with the key name. Let's say we have an associative list named poot, which is defined as
{fuzzy:"duck", ducky:"fuzz"}
To get our hands on the key ducky...
poot<ducky>
And so on and so forth.
Main object types
MESSo slightly resembles modern object-oriented languages, as everything is done by passing messages between different kinds of entities. How this is done, however, is slightly different (pathologically so.)
The objects in MESSo can be roughly categorized into two main types, permanent and transient. The following list shows the hierarchy of objects with the actual objects in bold.
- permanent
- nodes
- components
- nodes
- transient
- independent
- messages
- dependent
- anonymous components
- independent
So, as you can see, nodes can never be transient, whereas node components can be (in the form of anonymous components). Messages are transient but independent objects, whereas anonymous components are dependent on a node, i.e. they cannot be declared or used outside a node. Anonymous components can be defined inside permanent components or even other anonymous components, but permanent components can only be defined inside other permanent components.
There are no variables as such, but receivers contain folders which are used to store data.
If this all seems a bit confusing, don't worry, it doesn't get any better than this.
Nodes
Nodes are the main permanent objects. Every other permanent object in the language must be contained within a node.
A node can contain any number of the following components:
- message emitters (abbreviated to ME or emitter)
- message receivers (abbreviated to MR or receiver)
- anonymous message emitters (abbreviated anon emitter or ME.) These are found inside emitters.
Syntax for defining nodes, receivers and emitters:
NOTE: In code snippets all parts in italics are optional.
def_node(name) :: {type definitions} [ def_emitter(name) :: {type definitions} [ commands...; ] def_receiver(name) :: {type definitions} [ commands...; anon_emitter :: {type definitions} [ commands...; ] ] ]
Node type definitions
Node type definitions are of the format:
{key:numvalue, key:"stringvalue", key:{listval, "listval", ...}}
That means that different keys can take different types of values.
NOTE: true is defined as != 0, and false as == 0.
Here is a list of currently defined keys.
KEY TYPE MEANING active true/false Are components active yet, i.e. are they executed. Default: true category string The category the node belongs to (sort of analogous to classes). Multiple category keys can be specified. Default: world listen list Which node categories can send messages to the node. Default: world send list Which node categories can messages be sent to. Default: world
(In these definitions, world stands for all nodes)
For example:
def_node(myNode) :: {active:false, category:"myCategory", listen:"myCategory"} [ here be code; ]
This would define the node myNode, which would start out as inactive, belonging to the category myCategory and it would only receive data from other nodes of the same category. It is allowed to send data to all categories (send defaults to world)
Message emitters
An emitter's function is to send a message to a specified node.
An emitter can define a send list of its own. The emitter-specific list can't contain any nodes not already listed in its parent node's send list, but it can contain fewer items. For example, if an emitter's parent node had the send list {"node1", "node2", "node3", "node4"}, the emitter could have a list comprising only of {"node1", "node4"} but not {"node6", "node9"}.
By default, all node components inherit their type definitions from their parent node.
As you can see from the previous examples, the syntax for defining an emitter is the following
def_emitter(name) :: {type definitions} [ here be code; ]
These are the valid type definition keys for emitters
KEY TYPE MEANING active true/false Is the emitter active. Default: true send list Which node categories can messages be sent to. Default: inherited
The syntax for emitting a message inside an emitter (anonymous or permanent) is simply
emit(message, destination);
where message can be a string, list or number. destination is the destination of the message, and it's syntax is node.component.component... and so on, with as many components as necessary. So, the destination A.B.C.D would mean "receiver D which is inside receiver C which is inside receiver B which is inside node A".
A shorthand way of defining a anonymous message emitter is simply
value !> destination
For example
"Hello World!" !> otherNode.printOut;
This is equivalent to:
anon_emitter [ emit("Hello World!", otherNode.printOut); ]
Since all the type definitions were inherited from the node containing the statement, this message would get sent to the printOut receiver in the node otherNode. Assuming, of course, that the parent component and node can actually send messages to otherNode in the first place.
Remember, only emitters can emit messages or contain anonymous emitters. You can, however, define a permanent emitter inside a receiver.
Message receivers
Surprisingly, message receivers are used to receive messages from other nodes. Messages can be processed in a number of different ways, and then stored, forwarded or discarded. Receivers are only activated when they actually receive a message, which means that the code contained in them does nothing until a message is received.
The syntax for defining a receiver is the same as the one for emitters, i.e.
def_receiver(name) :: {type}
and so on.
These are the valid type definition keys for receivers
KEY TYPE MEANING active true/false Is the receiver active. Default: true listen list Which node categories can send messages to this receiver. Default: inherited read list Which node categories can read folders in this component. Default: parent node's category
Every node must contain a receiver with the name default which is the default end point for messages coming into the node. So if you send a message with just a node as the destination, it'll end up in the node's default receiver.
When a message is received, it goes into a folder. Remember the "dot notation" used with message emitters? That actually specifies a folder the message is supposed to go into. Only receivers can contain folders, but the folders they contain can be accessed by all other components in the node (and other nodes too if so specified.) So when you send a message to otherNode.otherRcv it gets shoved into a folder with the same name.
To complicate everything a bit further, the folders are actually lists, so that if 10 messages are sent to the same component, they get plonked into the folder in the order they arrived in (have a look at the list syntax in the next chapter).
Finally, to completely ruin everybody's sanity, even messages are a list, with the following syntax:
{messageid, source, message}
source is defined in the already familiar dot notation, so for example a message from the emitter plonk in the node quux would have a source of quux.plonk. messageid is an incremental value.
3 special constants are used to marginally increase readability.
ID == 0 SRC == 1 MSG == 2
Conditional statements and other fiddly bits
If
Rough draft at the moment
if(condition) [ code; ]
If condition evaluates to TRUE, execute code. condition is an expression, which can contain the following logical operators:
== != < > <= >= && || ^^
While-loops
while(condition) [ code; ]
As long as the expression condition evaluates to TRUE ( != 0), run code.
For-loops
Umm... I'll get back to you on that.
Notes
All of these are subject to change, and they quite often do.
Special built-in commands
I/O
print(string) prints string to STDOUT
getl(target) gets a \n-terminated line of text from STDIN and emits it to target. Can, naturally, only be used inside emitters. getc(target) gets a character and returns the ASCII value as an integer.
List manipulation
Lists can be manipulated in three ways, by using rotate(list), pop(list) or push(list, item). rotate non-destructively rotates the elements of the list one step to the right (so {1,2,3} would become {3,1,2}). pop removes the first element of the list. push appends item to the front of the list.
Associative list manipulation
Associative lists are manipulated with add(list, key:value) and delete(list, key). keylist(alist) returns a list containing all the keys in alist. The result must be emitted to be of any use.
Component manipulation
These commands are used to change components in different ways. Coming Real Soon Now
Hello World
def_node(hello) [ def_emitter(emitHW) [ "Hello, World!" !> hello; ] def_receiver(default) [ print(hello.default<MSG>); ] ]