Bidiroop
Bidiroop is an esolang by User:BoundedBeans where objects and classes have a different sort of relationship than usual.
Fundamentals
There are four first-class types in Bidiroop, two specific and two general.
- Unbounded integers (positive or negative)
- Strings
- Classes (describes the characteristics of its instances, which are objects)
- Objects (describes the characteristics of classes via returning classes from their methods dynamically)
Classes have methods, which cannot be accessed without an object of the type. All methods return classes, but can take any arguments. Objects have their own instance values, which classes can base off of. This effectively, but not technically, means that objects describe classes. In fact, objects fields cannot even be accessed directly except in their own methods, making objects effectively useless other than to describe classes through their methods. However, to allow functions that don't return classes, functions can be declared in a separate way not pertaining to objects.
Classes may contain:
- Inner classes (only accessible from the class's methods)
- Functions (only accessible from the class's methods)
- Fields (cannot be static but can have default values, only accessible from the class's methods)
- Methods (accessible from anywhere)
Methods may contain:
- Local classes
- Functions
- Local variables
All three of those are inaccessible outside the method.
Functions may contain the same things as methods, but they may be outside of classes. Inside of classes, they are static.
Fields and variables can contain any type, including classes. Any type may be a parameter to a function or method, and a function may return any type (methods can only return classes).
If a method or function gets passed the wrong type as a parameter, it will throw an error. However, functions can be overloaded to have different parameter types.
Methods and functions have access to direct members of any level that contains it, but not any indirect members. For example, say that there is a class named Foo
, containing a method named Bar
and an inner class named Baz
. Baz
contains method baz_event
and an inner class named Baz_Inner
. Bar
can make objects of Baz
(and also run their methods, since that qualifies as the object accessing it), but not Baz_Inner
. Bar
can also make objects of Foo
. However, baz_event
can make objects of Baz_Inner
.
All functions must have some return value, since they can only be run as part of an expression.
The program begins by executing the function named "main". This language should be garbage collected.
Syntax
This language uses indentation to store things, much like Python (the practical one, not the esolang). Unlike Python, the only valid indentation is two spaces, not more, not less.
Variable, field, class, method, and function names should contain only letters and underscores.
Statements
Import (filename)
This loads all functions and classes and their members into memory from the Bidiroop file.
If (integer)
Runs the indented code only if the integer is not zero.
If (string)
Runs the indented code only if the string is not empty.
Else
Runs the indented code if the previous condition was not met.
While (integer)
Repeats the indented code as long as the integer is not zero
While (string)
Repeats the indented code as long as the string is not empty.
Set (variable name), (expression)
Sets the variable to the expression. If the variable does not exist yet, create it. This can be put outside of functions to make global variables.
Delete (name)
Deletes the variable, function, or class.
Class (name)
Creates a class; the indented code should be made of Set commands (fields), method declarations, class declarations, and function declarations, comments, and nothing else.
Method (name)
Must be inside of a class. Creates a method, with the indented code being the content. After the name, there can optionally be a comma followed by a comma separated list of parameter declarations of the format (type) (name), where type can be "Integer", "String", "Class", "Any", or the name of a class to represent an object.
Function (name)
Creates a function, with the indented code being the content.. After the name, there can optionally be a comma followed by a comma separated list of parameter declarations of the format (type) (name), where type can be "Integer", "String", "Class", "Any", or the name of a class to represent an object.
Print (expression)
Prints a string or an integer.
InputInteger (name)
Inputs an integer into the named variable.
InputString (name)
Inputs a line of input into the named variable as a string.
Return (expression)
Returns a value from a method or function.
Comment (expression)
Ignored. Normally the expression should be a string literal, but it doesn't have to be.
Expressions
Expressions can be made of the following components:
Variable names:
Can be any variable the code has access to, or current
for the object calling the currently executing method or function.
Integer literals: same as in most other languages.
String literals: Text between double quotes. The following escape sequences are supported: \n for newline, \" for quote, \\ for backslash.
Objects: Goes something like this:
ClassName[my_var: 17 + 8, str: "Hello", x: -10349].
The variable names should be replaced with the actual parameter names specified in the class. They can be omitted to leave them at the default specified by the class.
Classes: Typed like this:
<C>("String").
The string should be a valid class definition (without the header) (the example is therefore not valid, just demonstrates syntax. It can however be an expression instead. This is pretty much the only way for a method to return a class, although the &* operator could be used to combine classes already specified somewhere. ). When creating a class this way, it is always treated as global. Classes that only exist in an expression and aren't assigned to anything will be deleted, unless they are the return value from a function or method, in which it will check if the function or method was called in an assignment statement, in which it will save the class in the variable.
Operators:
+ - addition - - subtraction * - multiplication / - division % - modulo ^ - exponent () - parenthesis for precedence, required for any more than one operator in an expression +* - string concatenation (can accept numbers to convert them to strings) ^% - discards the second argument, converts the first to a string &* - takes two classes, makes a new class with all methods, fields, inner classes, and functions from both. If there are duplicates, it discards the one from the second argument /& - logical NAND = - 1 if equal, zero if not (also works with strings) > - 1 if the first is greater than the second, zero if not. (less than can be accomplished with (((a = b) /& (a = b)) /& ((a > b) /& (a > b))) /& (((a = b) /& (a = b)) /& ((a > b) /& (a > b))) ) -/ - string cut off removing the position of the integer and afterwards /- - string cut off removing everything before the position of the integer -^ - ignores right, returns length of string
Method calls can be notated as <M>[(Object name)][(Method name)][(comma separated list of parameters)]
Such as <M>[my_object][strip_arguments][" get rid of this whitespace", "hello", "hi! "]
Function calls are the same, but they have no object name and the <M> is <F> instead.
Tricks
Data-containing objects
Objects can actually be used as containers of data as normal, but it's a bit weird. They will need getter and setter methods. Setter methods are simple, just set the value and return an empty class. Getters are weird, though. To make a getter you'll need to set a global variable to the value of an object field and return an empty class. Then other code can use the value of that global variable, which now contains the value of the object field. You can also use this as a general way to return things from methods that aren't classes.
First-class functions
Functions and methods are not first-class in Bidiroop, but by creating a class containing it from the text of the class, we can effectively emulate that.
Inheritance
Bidiroop does not have class inheritance, but it can be emulated with the &* operator, with the first class being the new content and the second class being the superclass. It will then evaluate to the class that is on the left, except that it extends the class on the right. Even normal overriding rules work if there are duplicate names!
Examples
Hello world
Function main Print "Hello, world!"
Calculator
Class OperatorConstruct Method a, op Return <C>(("Function apply, Integer a, Integer b\n Return a " +* op) +* " b\nMethod m, Integer a, Integer b\n Print <F>[apply][a, b]\n return <C>(\"\")") Function main InputInteger arga InputString operator InputInteger argb Set o, OperatorConstruct[] Set C, <M>[o][a][operator] Set oo, C[] Set mrun, <M>[oo][m][arga, argb]
Truth-machine
Function main InputInteger val If val Set TheClass, <C>("Method event\n Set x, 1\n While x\n Print \"1\"\n Return <C>(\"\")") Else Set TheClass, <C>("Method event\n Print 0\n Return <C>(\"\")") Set the_object, TheClass[] Set mrun, <M>[the_object][event][]
No object orientation:
Function main InputInteger val If val While val Print val Else Print 0
Deadfish interpreter
Set accumulator, 0 Class Deadfish Function i Set accumulator, accumulator + 1 If accumulator = 256 Set accumulator, 0 Return 0 Function d Set accumulator, accumulator - 1 If accumulator = -1 Set accumulator, 0 Return 0 Function s Set accumulator, accumulator ^ 2 If accumulator = 256 Set accumulator, 0 Return 0 Function o Print accumulator Return 0 Function main InputString code Set counter, 0 Set countermax, code -^ code Set total_class_str, "Method run\n" While countermax > counter Set piece, code -/ (counter + 1) Set piece, piece /- counter Set total_class_str, total_class_str +* (" Set dummy, <F>[" +* (piece +* "][]\n")) Set counter, counter + 1 Set total_class_str, total_class_str +* "Return <C>(\"\")" Set ConstructedClass, <C>(total_class_str) &* Deadfish Set the_object, ConstructedClass[] Set dummy, <M>[the_object][run][]
Brainfuck interpreter
This uses 3-cell unbounded brainfuck to prove Bidiroop Turing-complete, since Bidiroop does not have an array feature. Uses integer I/O rather than character. Code should be given as input, all on one line.
Set cella, 0 Set cellb, 0 Set cellc, 0 Set currentcell, 1 Class Brainfuck Function plus If currentcell = 1 Set cella, cella + 1 If currentcell = 2 Set cellb, cellb + 1 If currentcell = 3 Set cellc, cellc + 1 Return 0 Function minus If currentcell = 1 If currentcell > 0 Set cella, cella - 1 If currentcell = 2 If currentcell > 0 Set cellb, cellb - 1 If currentcell = 3 If currentcell > 0 Set cellc, cellc - 1 Return 0 Function left If currentcell = 1 Set currentcell, 3 Else Set currentcell, currentcell - 1 Return 0 Function right If currentcell = 3 Set currentcell, 1 Else Set currentcell, currentcell + 1 Return 0 Function input If currentcell = 1 InputInteger cella If currentcell = 2 InputInteger cellb If currentcell = 3 InputInteger cellc Return 0 Function output If currentcell = 1 Print cella If currentcell = 2 Print cellb If currentcell = 3 Print cellc Return 0 Function main InputString code Set counter, 0 Set countermax, code -^ code Set total_class_str, "Method run\n" Set spacing, 1 While countermax > counter Set piece, code -/ (counter + 1) Set piece, piece /- counter Set scounter, spacing While scounter Set total_class_str, total_class_str +* " " Set scounter, scounter - 1 If piece = ">" Set total_class_str, total_class_str +* "Set dummy, <F>[right][]\n" If piece = "<" Set total_class_str, total_class_str +* "Set dummy, <F>[left][]\n" If piece = "+" Set total_class_str, total_class_str +* "Set dummy, <F>[plus][]\n" If piece = "-" Set total_class_str, total_class_str +* "Set dummy, <F>[minus][]\n" If piece = "." Set total_class_str, total_class_str +* "Set dummy, <F>[output][]\n" If piece = "," Set total_class_str, total_class_str +* "Set dummy, <F>[input][]\n" If piece = "[" Comment "A really long way of saying 'While ((currentcell = 1) and (cella > 0)) or (((currentcell = 2) and (cellb > 0)) or ((currentcell = 3) and (cellc > 0)))'" Set total_class_str, total_class_str +* "While (((currentcell = 1) /& (cella > 0)) /& ((currentcell = 1) /& (cella > 0)) /& ((currentcell = 1) /& (cella > 0)) /& ((currentcell = 1) /& (cella > 0))) /& (((((currentcell = 2) /& (cellb > 0)) /& ((currentcell = 2) /& (cellb > 0)) /& ((currentcell = 2) /& (cellb > 0)) /& ((currentcell = 2) /& (cellb > 0))) /& (((currentcell = 3) /& (cellc > 0)) /& ((currentcell = 3) /& (cellc > 0)) /& ((currentcell = 3) /& (cellc > 0)) /& ((currentcell = 3) /& (cellc > 0)))) /& ((((currentcell = 2) /& (cellb > 0)) /& ((currentcell = 2) /& (cellb > 0)) /& ((currentcell = 2) /& (cellb > 0)) /& ((currentcell = 2) /& (cellb > 0))) /& (((currentcell = 3) /& (cellc > 0)) /& ((currentcell = 3) /& (cellc > 0)) /& ((currentcell = 3) /& (cellc > 0)) /& ((currentcell = 3) /& (cellc > 0)))))\n" Set spacing, spacing + 1 If piece = "]" Set spacing, spacing - 1 Set counter, counter + 1 Set total_class_str, total_class_str +* " Return <C>(\"\")" Set ConstructedClass, <C>(total_class_str) &* Brainfuck Set the_object, ConstructedClass[] Set dummy, <M>[the_object][run][]
Self-interpreter
Give the program as input, with the final line containing @END
Function main Set code, "" Set line, "" While (line = "@END") /& (line = "@END") Set code, code +* (" " +* (line +* "\n")) InputString line Set class_string, "Method __code__" Set class_string, class_string +* (code +* " <F>[main][]\n Return <C>(\"\")") Set ClassedCode, <C>(class_string) Set an_object = ClassedCode[] Set dummy, <M>[an_object][__code__][]
Data structures
Data structures are hard to make in Bidiroop, but not impossible. The main issue is that objects cannot make their fields available through returning. However, this is still possible by setting a global variable to the object field's value. We'll call this global variable globereturn
. This way we can implement a getter. We can implement a setter much easier by passing a parameter, and having the method set the value. For both of these, the actual return value of the method will be an empty class.
Stack
The stack is the base for a lot of other data structures. Here is the stack class:
Class Stack Set value, 0 Set link, 0 Comment "Stores new stack element with the thing to push as the element and the link being the element you called the method on" Comment "You will have to get the value from globereturn" Method push, Any thing_to_push Set globereturn, Stack[value: thing_to_push, link: current] Return <C>("") Comment "Returns the linked element; also get the value from globereturn" Method pop Set globereturn, link Return <C>("") Method get_value Set globereturn, value Return <C>("")
Push and pop are the base operations needed. To pop and return the value, you'll need to use get_value
, put the value in globereturn
somewhere else, then pop. Using just get_value
is equivalent to a peek. From these you can form duplicate, swap, roll, etc.
List with pointer
A list with a pointer can be accomplished with two stacks, and a delimiting value for each end. I used @FIRST
and @LAST
for the delimiting values, but you could use anything.
Class List Set stacka, Stack[value: "@FIRST", link: 0] Set stackb, Stack[value: "@LAST", link: 0] Set pointed, 0 Comment "left, right, set_value, insert, and remove_pointed modify in place; there isn't any need to pick up the result from globereturn" Comment "for get_value and check_bounds you WILL need to get the result from globereturn" Method left Set dummy, <M>[stackb][push][pointed] Set stackb, globereturn Set dummy, <M>[stacka][get_value][] Set pointed, globereturn Set dummy, <M>[stacka][pop][] Set stacka, globereturn Return <C>("") Method right Set dummy, <M>[stacka][push][pointed] Set stacka, globereturn Set dummy, <M>[stackb][get_value][] Set pointed, globereturn Set dummy, <M>[stackb][pop][] Set stackb, globereturn Return <C>("") Method set_value, Any value_to_set Set pointed, value_to_set Return <C>("") Method get_value Set globereturn, pointed Return <C>("") Method insert, Any value_to_insert Set dummy, <M>[stacka][push][value_to_insert] Set stacka, globereturn Set dummy, <M>[current][left][] Return <C>("") Method remove_pointed Set dummy, <M>[stackb][get_value][] Set pointer, globereturn Set dummy, <M>[stackb][pop][] Set stackb, globereturn Return <C>("") Comment "Returns 1 if pointer on the border, 0 if not" Method check_bounds If pointed = "@FIRST" Set globereturn, 1 Else If pointed = "@LAST" Set globereturn, 1 Else Set globereturn, 0 Return <C>("")
Now we have everything we need for a list with a pointer. This enables us to make a deque.
Deque
Class Deque Set mainlist = List[] Comment "remove_first and remove_last also retrieve the value and put it in globereturn. All methods modify in place." Method add_first, Any value_to_add While <M>[mainlist][check_bounds][] = 0 Set dummy, <M>[mainlist][left][] Set dummy, <M>[mainlist][right][] Set dummy, <M>[mainlist][insert][value_to_add] Return <C>("") Method add_last, Any value_to_add While <M>[mainlist][check_bounds][] = 0 Set dummy, <M>[mainlist][right][] Set dummy, <M>[mainlist][insert][value_to_add] Return <C>("") Method remove_first While <M>[mainlist][check_bounds][] = 0 Set dummy, <M>[mainlist][left][] Set dummy, <M>[mainlist][right][] Set dummy, <M>[mainlist][get_value][] Set deque_local_value_holder, globereturn Set dummy, <M>[mainlist][remove_pointed][] Set globereturn, deque_local_value_holder Return <C>("") Method remove_last While <M>[mainlist][check_bounds][] = 0 Set dummy, <M>[mainlist][right][] Set dummy, <M>[mainlist][left][] Set dummy, <M>[mainlist][get_value][] Set deque_local_value_holder, globereturn Set dummy, <M>[mainlist][remove_pointed][] Set globereturn, deque_local_value_holder Return <C>("")
Queue
A queue can be implemented simply by using only opposite sides of the deque. Funnily enough, you can actually make a stack from a deque too, making a totally pointless stack made out of two stacks.
Pair
Pair is separate from the rest of them, and is pretty easy.
Class Pair Set valuea, 0 Set valueb, 0 Method set_a, Any value_to_set Set valuea, value_to_set Return <C>("") Method set_b, Any value_to_set Set valueb, value_to_set Return <C>("") Method get_a Set globereturn, valuea Return <C>("") Method get_b Set globereturn, valueb Return <C>("")
Dictionary
This only works with strings, but if you added some sort of equals method to a class, you could make another class kind of like this one using different keys. It does not actually hash the values, merely checks every single one of them until it finds it. It uses a list of pairs to accomplish this.
Class Dictionary Set mainlist, List[] Method set, String k, Any v Set key_found, 0 While <M>[mainlist][check_bounds][] = 0 Set dummy, <M>[mainlist][get_value][] Set dictionary_local_value_holder, globereturn Set dummy, <M>[dictionary_local_value_holder][get_a] If globereturn = k Set dummy, <M>[mainlist][set_value][Pair[k, v]] Set key_found, 1 Set dummy, <M>[mainlist][right][] If key_found = 0 Set dummy, <M>[mainlist][insert][Pair[k, v]] Set dummy, <M>[mainlist][left][] While <M>[mainlist][check_bounds][] = 0 Set dummy, <M>[mainlist][left][] Set dummy, <M>[mainlist][right][] Return <C>("") Comment "To check if a key exists, use the get method and check that the value is not '@KEY_NOT_FOUND'" Method get, String k Set key_found, 0 While <M>[mainlist][check_bounds][] = 0 Set dummy, <M>[mainlist][get_value][] Set dictionary_local_value_holder, globereturn Set dummy, <M>[dictionary_local_value_holder][get_a] If globereturn = k Set dummy, <M>[mainlist][get_value][] Set key_found, 1 Set dummy, <M>[mainlist][right][] If key_found = 0 Set globereturn, "@KEY_NOT_FOUND" Set dummy, <M>[mainlist][left][] While <M>[mainlist][check_bounds][] = 0 Set dummy, <M>[mainlist][left][] Set dummy, <M>[mainlist][right][] Return <C>("") Method remove, String k While <M>[mainlist][check_bounds][] = 0 Set dummy, <M>[mainlist][get_value][] Set dictionary_local_value_holder, globereturn Set dummy, <M>[dictionary_local_value_holder][get_a] If globereturn = k Set dummy, <M>[mainlist][remove_pointed][] Set dummy, <M>[mainlist][right][] Set dummy, <M>[mainlist][left][] While <M>[mainlist][check_bounds][] = 0 Set dummy, <M>[mainlist][left][] Set dummy, <M>[mainlist][right][] Return <C>("")
Summary
We've basically constructed a stack through a singly-linked list concept. We construct a simple ordered pair as well. We can take two stacks to form a list with a pointer. Using that list, we can construct a few algorithms for the list to work like a deque. With a deque, we can have a queue. Also, a list of pairs, combined with certain algorithms, can make a dictionary from strings to other data types. It is also fully possible to combine these to nest them. We can use list objects as the element of another list to create a matrix. Or we can include a stack object as the values of a dictionary to construct a mapping from strings to stacks. Or nest dictionaries, or whatever else. Trees are really the only base data structure we haven't done, but a binary tree could be a ton of nested pairs, and something bigger could be nested dictionaries with the keys as child identifiers, paths encoded as a list of strings.