Thrillodendron
Thrillodendron is an esolang by User:BoundedBeans featuring object orientation completely contained within a string syntax.
Syntax
The entire program is stored within a quoted string (representing a method), and all data types are also represented as strings. Strings must be put in double quotes, single quotes do not work. This makes it pretty hard to actually do anything, unless we introduce escape sequences. Unlike most other languages, Thrillodendron uses the power sign (^) as the escape character, rather than the backslash. The backslash is completely usable as a normal character, but the power sign isn't. There are only three escape sequences. The two main ones are ^" (escaped double quote) and ^^ (escaped power sign). There is also a third, ^c(four digit decimal number), which takes four decimal digits (padded with zeroes if shorter, cannot be longer), interprets as a number, and ignores that many characters ahead of it. This allows for comments (up to 9999 characters, anything above that just insert another ^c and continue the comment, ideally not interrupting the flow of the text). This mainly will be used in the top level string, but you could use something like ^^c(whatever) to escape strings of deeper levels. Spaces, tabs, newlines, vertical tabs, carriage returns, and form feeds are useless to strings and will not be transcribed into the string, so they can be used for nice formatting. This does mean that these characters won't count towards the number of ignored characters for ^c, so be careful.
Data types
All data types are represented in a string syntax, but they are not all identical. They are all first class. The data types are:
- Unbounded non-negative integers; represented by "I" + decimal representation
- List, represented by "L" + elements (themselves represented by strings) separated by commas (the empty list is just "L")
- Methods; represented by "M" + method body. Parameters and return values can be simulated by storing a value into a variable reference and retrieving a value from it on the other side.
- Classes; represented by "C" + list of settable instance default values (the key is 1 concatenated with the zero based index (so the third element is accessed with "I12")) (this can include methods and inner classes that objects can have individually) + list of unsettable methods (key is 2 concatenated with the zero based index) + list of unsettable inner classes (key is 3 concatenated with the zero based index) + either the empty string or a class to extend. Instance values include methods and inner classes.
- Objects; represented by "O" + class (can be a variable reference) + list of current values (current values can be the empty string to keep it at the default)
- Variable reference; represented by "V" + any combination of characters for the name (following escape rules of course). When used, it returns the value it references. If they don't reference anything, they return the integer 0, though they really should always reference something except in the A command. Once the A command is used, it will always have a reference.
- Object instance value accessor; represented by "X" + object + integer key. These can be nested to access multiple layers.
- This (refers to the object currently running this method); represented by "T". If the method is not "owned" by an object, return the integer 0.
- The empty string (only useful for some empty type arguments)
Notes:
- Both variable references and object instance value accessors can be used anywhere a normal variable can, as long as the type matches.
- All variable references are global.
- There is no lone string data type that can represent any text, despite everything being a string. You'll have to represent that as a list of unicode values.
- All arguments within data is represented also by a string, so heavy escaping is required. For example, an class with a single instance integer and nothing else could be represented as
"C^"L^^^"I0^^^"^"^"L^"^"L^"^"^""
- The code is represented in one big method.
- In the A, B, C, D, E, F, H, I, L, N, O, P, Q, and R commands, a reference or accessor is "assigned". For a reference, that means that using the same name again in a reference literal will retrieve the value assigned. For an accessor, this actually changes the values of the object to something else.
Method bodies
A method body is represented by a list (not the datatype, just a convenient word for it) of commands, ended by semicolons, the arguments preceded by colons. The commands are:
A:(variable reference):(value);
Sets the variable reference to refer to the value. This can also be used with an object instance value accessor to change instance values of an object, though it can only be used this way with settable values, not unsettable methods or classes, meaning the integers will always start with 1 if used in this manner. The value can be anything. The first argument is treated as a key rather than a value. However, in the second argument, putting a reference or accessor causes them to reference the same value (it does not cause a reference to a reference).
B:(value 1):(value 2):(variable reference);
If both values are integers, adds the two together and makes the reference reference the result. If the first is a list and the other is something else, append the value to the list instead and store. If the second is a list and the other is something else, prepend and store. If both are lists, concatenate them in the order they were put in as arguments. The third argument is treated as a key rather than a value.
C:(value 1):(value 2):(variable reference);
If both values are integers, does 1st - 2nd and stores. If the result is negative, store the absolute value. If the first is a list and the second is an integer, set the reference to the value at the integer index in the list. The third argument is treated as a key rather than a value.
D:(value 1):(value 2):(variable reference);
Like B and C, but multiply. Both values must be integers. The third argument is treated as a key rather than a value.
E:(value 1):(value 2):(variable reference);
Like B and C, but divide. Both values must be integers. The third argument is treated as a key rather than a value. If the second argument is zero, return 0.
F:(value 1):(value 2):(variable reference);
Like B and C, but modulo. Both values must be integers. The third argument is treated as a key rather than a value. If the second argument is zero, return 0.
B, C, D, E, and F can also use accessors instead of references.
G:(value 1);
Print the value as an integer if it is an integer. Print the value as a string of UTF-16 characters if it is a list containing only integers. Any type other than those or a reference or accessor to those will throw an error.
H:(variable reference);
Input an integer to the variable reference. The argument is treated as a key rather than a value. If there is no more input, put a zero.
I:(variable reference);
Input a line of input as a list of UTF-16 characters. The argument is treated as a key rather than a value. If there is no more input, put the empty list.
H and I can also use accessors instead of references.
J:(value);
If the value is zero or a reference or accessor to it, jump forward to the matching K.
K:(value);
If the value is anything except the integer zero or a reference or accessor to it, jump backward to the matching J. J and K match like parenthesis, and in most situations should have the same value checked but don't have to. Most likely you would want to check a reference or accessor here, but you could check 1 to make an infinite loop, or 0 to skip, or something like that.
L:(value):(variable reference);
If the value is a list of only integers, interpret it as UTF-16 character codes, evaluate it as a Thrillodendron literal, and set the reference to it. The literal does not need outer quotes or top-level escapes, but any inner values will. This can also use an accessor instead of a reference. The second argument is treated as a key rather than a value. This can be used with a method literal to execute code dynamically.
M:(value);
If the value is a method, run it. This can be used with an object accessor to run an object's methods.
N:(class):(variable reference);
Creates a new object of the class, stores it into the reference or accessor. The second argument is treated as a key rather than a value.
O:(variable reference):(variable reference)
Duplicates the value of the first reference or accessor and puts it in the second, rather than causing two references to the same value. This allows a value to be used more than once and have different modifications each time.
P:(value):(variable reference);
If the value is a list containing only integers, interpret it as UTF-16, use it as a filename to a Thrillodendron file, store the contents into the reference or accessor. Note that the content of the file specified does not need to be a method, it could be a class, list, or any other data-type, but it must be a literal. Non-method files cannot directly be executed.
Q:(value):(value):(variable reference);
Sets the reference or accessor to 1 if the two values are the same type, 0 if not. If both values are objects, it will return 1 if they are different classes, 2 if they have the same class or if their classes have the exact same content.
R:(value):(variable reference);
Sets the reference or accessor to the length of the first argument if the first argument is a list.
Examples
Hello world
"MG:^"L^^^"I72^^^",^^^"I101^^^",^^^"I108^^^", ^^^"I108^^^",^^^"I111^^^",^^^"I32^^^",^^^"I11 9^^^",^^^"I111^^^",^^^"I114^^^",^^^"I108^^^", ^^^"I100^^^",^^^"I33^^^"^";"
Truth-machine
"MH:^"VTruth^";J:^"VTruth^";G:^"I1^";K:^"VTruth^";G:^"I0^";"
Cat
"MA:^"V1^":^"I1^";J:^"V1^";I:^"VString^";G:^"VString^";K:^"V1^";"
Bitwise Cyclic Tag Interpreter
Accepts the initial queue, then the instructions, on separate lines.
"M A:^"VQueueClass^": ^"C ^^^"L ^^^^^^^"L ^^^^^^^^^^^^^^^"I49 ^^^^^^^^^^^^^^^" ^^^^^^^" ^^^" ^^^"L ^^^^^^^"M R:^^^^^^^^^^^^^^^"X ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"T^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"I10^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ^^^^^^^^^^^^^^^":^^^^^^^^^^^^^^^"VQueueLength_^^^^^^^^^^^^^^^"; A:^^^^^^^^^^^^^^^"VUsefulList_^^^^^^^^^^^^^^^":^^^^^^^^^^^^^^^"L^^^^^^^^^^^^^^^"; J:^^^^^^^^^^^^^^^"VQueueLength_^^^^^^^^^^^^^^^"; C:^^^^^^^^^^^^^^^"T^^^^^^^^^^^^^^^":^^^^^^^^^^^^^^^"VQueueLength_^^^^^^^^^^^^^^^" :^^^^^^^^^^^^^^^"VCurrent_^^^^^^^^^^^^^^^"; B:^^^^^^^^^^^^^^^"VCurrent_^^^^^^^^^^^^^^^":^^^^^^^^^^^^^^^"VUsefulList_^^^^^^^^^^^^^^^" :^^^^^^^^^^^^^^"VUsefulList_^^^^^^^^^^^^^^^"; K:^^^^^^^^^^^^^^^"VQueueLength_^^^^^^^^^^^^^^^"; O:^^^^^^^^^^^^^^^"VUsefulList_^^^^^^^^^^^^^^^" :^^^^^^^^^^^^^^^"X ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"T^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"I10^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ^^^^^^^^^^^^^^^"; ^^^^^^^" ^^^" ^^^"L^^^" ^^^"^^^" ^" ; A:^"VUsefulList^":^"L^"; I:^"VPreQueue^"; A:^"VQueue^":^"O^^^"VQueueClass^^^"^^^"VPreQueue^^^"^"; I:^"VCode^"; A:^"VCounter^":^"I0^"; J:^"I1^"; C:^"VCode^":^"VCounter^":^"VCurrent^"; C:^"VCurrent^":^"I49^":^"VSubCurrent^"; J:^"VSubCurrent^"; M:^"X^^^"VQueue^^^"^^^"I20^^^"^"; K:^"I0^"; C:^"VCurrent^":^"I48^":^"VSubCurrent^"; J:^"VSubCurrent^"; B:^"VCounter^":^"I1^":^"VCounter^"; C:^"VCode^":^"VCounter^":^"VCurrent^"; C:^"VQueue^":^"I0^":^"VFront^"; C:^"VFront^":^"I48^":^"VFront^"; J:^"VFront^"; B:^"X^^^"VQueue^^^"^^^"I10^^^"^":^"VCurrent^":^"X^^^"VQueue^^^"^^^"I10^^^"^"; K:^"I0^"; K:^"I0^"; B:^"VCounter^":^"I1^":^"VCounter^"; R:^"X^^^"VQueue^^^"^^^"I10^^^"^":^"VQueueLength^"; C:^"VCounter^":^"VQueueLength^":^"VTestCounter^"; J:^"VTestCounter^"; A:^"VCounter^":^"I0^"; K:^"I0^"; K:^"I1^"; "