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.