Detrovert
| Paradigm(s) | Graph-rewriting, Object-oriented |
|---|---|
| Designed by | User:Hakerh400 |
| Appeared in | 2020 |
| Computational class | Turing complete |
| Major implementations | Interpreter |
| File extension(s) | .txt |
Detrovert is a graph-rewriting and object-oriented esoteric programming language.
Overview
Classes and objects
In this language everything is an object. There is a constant number of classes that are defined at the beginning of the program and each object that is created is an instance of a class. Objects can be constructed explicitly, but they can be destructed only implicitly by a garbage collector.
Each class is either abstract or concrete. Abstract classes can be extended, but cannot instantiated, while concrete classes can be instantiated, but cannot be extended. A class (both abstract and concrete) may have any number of attributes. All attributes are public. Classes do not contain methods. Two attributes with the same name cannot appear in the same class, nor in any class that is in the prototype chain. Each attribute has a type, which is a class.
There is an object called nil. It is a unique object and it can be assigned to any attribute of any class. Also, nil is the default value for any attribute that has no explicitly specified value. nil does not have any attributes. All nil values are equal.
Control flow
Reading attributes from an object is performed by destructuring and pattern-matching. This also implies that nil cannot be destructured in any way, because it has no attributes. Comparing two objects is also achievable by pattern matching and naming objects with the same identifier in the pattern. Non-extensional equivalence is used in comparing objects.
Source code consists of class definitions and patterns. Each pattern consists of what objects to find and how to transform the found objects.
Execution step
Execution is performed by a collection of threads. Initially, there is only one thread. A thread can spawn any number of other threads at any execution step. Also, each thread must re-spawn itself if it wants to continue, otherwise, it will be implicitly terminated. Each thread is bound to a single object. A thread itself is not an object.
There is a queue of threads. When the queue becomes empty, the program halts. Otherwise, pop the first (least recently added) thread from the queue. Find the last pattern from the source code that applies to the object to which the thread is bound. If there is no such object, skip this execution step. Otherwise, apply the transformations from the pattern. Transformations may contain information about what new threads need to be spawned. The thread that we popped from the queue is terminated in any case. So, each thread can perform just a single execution step if it does not re-spawn itself (and by re-spawning we mean to spawn another thread that is bound to the same object).
Native classes
There are several native classes. They exist in every program. User classes cannot be extended from native classes, except implicitly from the base class.
Names of native classes start with dot character. Native classes are:
.Base- The base class. All classes are extended from it. It is not extended from anything. It has no attributes..String- Represents a binary string. Contains attributebitof type.Bit..Bit- Abstract class representing a bit. It has attributenextof type.Bitwhich point to the next bit, ornil..Bit0- Extends.Bitand represents bit 0..Bit1- Extends.Bitand represents bit 1.
I/O format
Input and output are strings of bits. When program starts, the input string is represented as a linked list using native classes and a single thread is bound to the string object. When program halts, the output is parsed from the main string object (interpreter keeps an extra reference to the main string to prevent it from being garbage-collected).
Syntax
Source code consists of two parts: class definitions and patterns.
Class definitions appear at the beginning of the source code in a pair of parentheses. Inside, all user-defined classes are specified. Extended class is represented by ~. It should be clear from this example:
(
MyClass(
myAttribute TypeOfTheAttribute
someOtherAttribute SomeOtherType
)
TypeOfTheAttribute ~ MyClass(
recursiveAttribute MyClass
myString .String
)
SomeOtherType ~ MyClass(
// No attributes
)
)
After we define classes, we specify all patterns. Each pattern contains two parts: 1) what to find and 2) how to transform it. These two parts are separated by -> arrow. Example:
(
MyClass x(
myAttribute y
)
->
x(
myAttribute nil
)
)
This means: if the current thread is bound to an object of type MyClass (we name it x) and the object x contains attribute myAttribute that is some y, then set myAttribute of x to nil.
To spawn a new thread, put * symbol before identifier name in the second part of the pattern.
Syntax allows a lot of possibilities, which would take a lot of time to explain. See the implementation for more details.
Examples
Cat program
()
Invert bits
() ( .String a(bit b) .Bit0 b(next c) -> a(bit d) .Bit1 *d(next c) ) ( .String a(bit b) .Bit1 b(next c) -> a(bit d) .Bit0 *d(next c) ) ( .Bit a(next b) .Bit0 b(next c) -> a(next d) .Bit1 *d(next c) ) ( .Bit a(next b) .Bit1 b(next c) -> a(next d) .Bit0 *d(next c) )
Reverse string
(
Reverse(
str .String
in .Bit
out .Bit
)
)
(
.String a(bit b)
->
Reverse *c(
str a
in b
out nil
)
)
(
Reverse a(
in b
out c
)
.Bit0 b(next d)
->
*a(
in d
out e
)
.Bit0 e(next c)
)
(
Reverse a(
in b
out c
)
.Bit1 b(next d)
->
*a(
in d
out e
)
.Bit1 e(next c)
)
(
Reverse a(
str b
in nil
out c
)
.String b()
->
b(bit c)
)