MESSo

From Esolang
Jump to navigation Jump to search

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


  • transient
    • independent
      • messages
    • dependent
      • anonymous components


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>);
    ]

]