Ende

From Esolang
Jump to navigation Jump to search

Ende is an esoteric programming language. It is a modern object-oriented language inspired by Glass and JavaScript that has a rich prototype-based object model and (un)intuitive queue-based semantics. Some of its concepts are also borrowed from the Sve programming language.

The basic idea of the language is that the execution environments (scopes) are first-class objects as in Sve. In fact, all objects can and must be used as environments in order to manipulate them.

Reasons to use this language

The concepts of this language are supposed to be painful but clearly possible to understand. These concepts include:

  • Queue semantics as opposed to traditional stacks. Queues are not necessary harder to understand than stacks, but they require some practicing. For example, the stack-based program 1 2 + 3 4 + * would be 1 2 3 4 + + * in a queue-based language.
  • An object can be directly manipulated only if the object is the current environment object. Subenvironments are created when not needed. The prototype and parent hierarchies are complex and the programmer must be aware of them always.
  • The abuse of asynchonous methods and promises brings into light the best and worst sides of JavaScript.

The author thinks that these concepts are beautiful in theory, but in practice make programming irritating. They are concepts that should not be used in serious programming languages, although this has been done in smaller scale in for example JavaScript and Sve. The author invites anyone who disagrees to try programming using Ende.

Object model

Each object contains always:

  • a hash map from strings to objects
  • a queue of objects

Additionally an object can be primitive and contain other data. For example, Integer objects contain their integer value.

This can be summarized by the following pseudocode declaration:

class EndeObject {
  Map<String, EndeObject> map;
  Queue<EndeObject> queue;
}

class EndeInteger extends EndeObject {
  int value;
}

// other primitive types...

Magic keys

The hash map can contain the following special keys:

Key Description
__bool__ The boolean value of a non-primitive object is determined to be the boolean value of __bool__.
__name__ The name of the object (used to create the string representation).
__parent__ The parent of the object.
__proto__ The prototype of the object.

Prototype hierarchy

Ende is a prototype-based language. The prototype of each object is stored with the __proto__ key in the hash map. It is not required that each object has a prototype, but an object is useless without a prototype.

The objects form a tree of prototypes called the prototype hierarchy. At the top of the tree is Nil, which is the only builtin object that has no prototype. Below that is Object, which contains all the basic methods required to manipulate the objects. All other objects should be below it, like Integer and String are.

If a key is not found in the hash map of an object, it will be searched from the prototype (and its prototype recursively if needed).

Evaluation model and the parent hierarchy

An Ende program is a list of commands, each which can be a method call, a string literal or an object literal. Every command is evaluated on an object, called the environment. At begining of the program the environment is the Object object.

The simplest command, string literal, simply creates a new primitive String object and pushes it to the queue of the environment.

[this is string literal]

When a method call is evaluated, first the body of the method is searched from the hash map of the environment. Then a new subenvironment is created and the body is evaluated in that environment.

[the d method will print this string]d
[a method could have a longer name in parentheses](this is a long method name)

When an object literal is evaluated, a new subenvironment is created and the code inside the literal is evaluated in that subenvironment. Then the subenvironment is pushed to the queue of the parent environment.

{[desc][this is the value of the "desc" key in the hash map. it is assigned using the = command]=}

A subenvironment will have its __proto__ and __parent__ keys assigned to its parent environment, that is, the environment where it was created. The parent objects form a tree called the parent hierarchy. All user-created objects will have a parent, while none of the builtin objects do.

All commands with the exception of %, > and & modify only the queue of the current environment.

Method calls

Because the it is only possible to call methods of the current environment, the only way to access methods of other objects is to evaluate code on them using the : command.

For example, to concatenate to strings, the . method defined in the String prototype should be used. This can be done by pushing the second string to the queue of the first string object and then evaluating code containing the method call on the first string. Lastly, the result must be pulled from the queue of the first string to the original environment.

[Hello, ][World!]<>[.]<:&d
[Hello, ]                  Push "Hello, " Queue: "Hello, "
         [World!]          Push "World!"  Queue: "Hello, " "World!"
                 <         Duplicate      Queue: "Hello, " "World!" "Hello, "
                  >        Push to        Queue:                    "Hello, "
                   [.]     Push "."       Queue:                    "Hello, " "."
                      <    Duplicate      Queue:                    "Hello, " "." "Hello, "
                       :   Evaluate on    Queue:                                  "Hello, "
                        &  Pull from      Queue:                                            "Hello, World!"
                         d Print

The > command is used to push "World!" to the queue of "Hello, ". After the . method is called using :, the result is pulled from the queue using the & command.

Of course, it is not always necessary to evaluate code on the object itself. This is the case for example when the method will not modify the object and the object is not primitive. In these cases it might be easier to create a new subenvironment using the ^ command and change its prototype to the object. This way we can access the methods via the prototype. As the bodies of the methods will be evaluated in this subenvironment, they will not modify the original object, even if they try.

Classes and object initialization

There are no classes, only prototypes. In a way, the prototype is a class, but that is stretching. Is the prototype instance of itself? Are the subclasses instances of the classes they inherit? Are subenvironments instances of the objects/classes they inherit?

Anyway, to create a "class" it is enough to create an object that contains method definitions like this:

[Person]{
  [__name__][Person]=
  [printName][[name]?d]=
}=

The __name__ field is used to set the name of the class. The class has one method, printName that will print the string in the name key.

To create a person object, the __proto__ field of a new object must be set to be the Person prototype:

[suzie]{[Person][__proto__]?=[name][Suzie]=}=

The code will create a new object and assign it to the suzie field of the current object.

To call the printName, we can use either the : or the ^ command. Both of these will work, as the method does not modify the object.

[suzie]?[(printName)]:
[[suzie][__proto__]?=(printName)]^

Control structures

The flow control is based on callbacks. The i command will create a promise that is successful only if the value it pulled was truthy.

For example, the following code tests if a and b are equal. Note that the methods of the promise must be called using : as they modify the internal state of the promise object.

[a][b]??<>[-]<:&
i
{}<>[,[They are not equal]d]<>[(then)]<:
{}<>[,[They are equal]d]<>[(catch)]:

It first subtracts them and then creates the promise using i. The promise will fail if they are equal, because 0 is falsy and other integers are truthy.

The only loop available in Ende is the for-each loop of the List prototype. For example, to print the numbers from 0 to 10, it is necessary to create a list of them using the (range) method and then call the f method of the list.

##[10]<:(range)
[$[d]f]:

Reference

Method/field name Description
Nil
__proto__ Nil does not have a prototype.
__name__ "Nil"
__bool__ 0
Object
__proto__ The prototype of Object is Nil.
__name__ "Object"
__bool__ 1
< Duplicate. Pulls a value from the front of the queue and pushes it back.
, Discard. Pulls a value and discards it.
@ Parent push. Pulls a value from the queue of this object and pushes it to the queue of the parent object.
% Parent pull. Pulls a value from the queue of the parent of this object and pushes it to the queue of this object.
> Push. Pulls an object and a value and pushes the value to the queue of the object.
& Pull. Pulls an object and pulls a value from its queue, then pushes the value to the queue of this object.
$ Self. Pushes this object to its own queue.
? Get. Pulls a key and pushes its value in the hash map to the queue.
= Set. Pulls a key and a value and puts them to the hash map.
^ Evaluate. Pulls a string and evaluates it in a new subenvironment of the current environment.
: Evaluate on. Pulls an object and a string and evaluates the string on the object.
# Pushes the primitive object 0.
i If. Pulls a value. Returns a promise that, if the value is truthy, will success, and otherwise fail.
d Print. Pulls an object and prints its string representation.
String The string prototype.
Integer The integer prototype.
List The list prototype.
Promise The promise prototype.
String
__proto__ The prototype of String is Object.
__name__ "String"
. Concatenate. Pulls a string and concatenates it to this string.
Integer
__proto__ The prototype of Integer is Object.
__name__ "Integer"
+ Add. Pulls an integer and adds it to this integer.
- Subtract. Pulls an integer and subtracts it from this integer.
* Multiply. Pulls an integer and multiplies it with this integer.
/ Add. Pulls an integer and divides it with this integer.
0 Multiplies this object by 10 in place.
1 Multiplies this object by 10 and adds 1 in place.
2 Multiplies this object by 10 and adds 2 in place.
3 Multiplies this object by 10 and adds 3 in place.
4 Multiplies this object by 10 and adds 4 in place.
5 Multiplies this object by 10 and adds 5 in place.
6 Multiplies this object by 10 and adds 6 in place.
7 Multiplies this object by 10 and adds 7 in place.
8 Multiplies this object by 10 and adds 8 in place.
9 Multiplies this object by 10 and adds 9 in place.
List
__proto__ The prototype of List is Object.
__name__ "List"
f Pulls an object and a string and then, for each item in the list, pushes the item to the queue of the object and evaluates the string on the object.
length Pushes the length of the list to the queue.
Promise
__proto__ The prototype of Promise is Object.
__name__ "Promise"
ok Pulls a value and makes this promise success with that value (evaluating the then callback).
fail Pulls a value and makes this promise fail with that value (evaluating the catch callback).
then Pulls an object and a string. After ok is called, the string will be evaluated on the object.
catch Pulls an object and a string. After fail is called, the string will be evaluated on the object.

Examples

Hello world

[Hello, world!]d

Cat program

{[List][__proto__]?=[0]#=}[$[<![#[1]<:+]<:&<=l{}<>[d]<>[(then)]:]f]:

External links