This is the interpreter for the perfect programming language. It is made in Python, for the sole reason that the interpreter can itself be interpreted. Future plans include creating a DreamBerd interpreter in DreamBerd, so that the DreamBerd Interpreter can be passed into the DreamBerd Interpreter Interpreter, which is then interpreted by the DreamBerd Interpreter Interpreter Interpreter (a.k.a. Python). This may or may not be created due to difficulty moving everything over and whatnot. I'll try though.
This is incredibly slow. My implementation of DreamBerd is suboptimal, which itself runs on a subperformant language (Python), which runs on a pretty fast language (C). However, speed was never a focus in creating my interpreter for DreamBerd and shouldn't be - it's not a language meant for day-to-day use - it's a work of art.
You can install DreamBerd from PyPi, by doing any the following:
$ pip install dreamberd
$ pip install "dreamberd[input, globals]"
$ pip install "dreamberd[input]"
$ pip install "dreamberd[globals]"
Each of these commands installs DreamBerd with the respective dependencies. input
installs the pynput
package and allows the use of after
statements and event watchers. globals
installs PyGithub
and allows you to declare const const const
variables that are publically stored using GitHub. Note: to use the latter, you must enter a Personal Access Token in the GITHUB_ACCESS_TOKEN
environment variable.
Now that you have installed DreamBerd, you can run the REPL using the $ dreamberd
command, or you can run a file using $ dreamberd FILE
. Usage instructions here:
usage: dreamberd [-h] [-s] [file]
positional arguments:
file the file containing your DreamBerd code
options:
-h, --help show this help message and exit
-s, --show-traceback show the full Python trackback upon errors
- Add another expression type which is just the dot operator, used for indexing and accessing names
- Better debugging (pretty limited for the time being)
- A much better standard library
- Allow for declaring basic objects with {} and other things.
- Add a way to deal with file objects
The goal of this project is to implement every feature from the DreamBerd language. A list of features is in the README file of the project, linked here. Here is a working list of features that there is no chance I will implement (new features may be added - or I should say, removed - as I work on this project and realize I'm too stupid to implement them):
-
DB3X: I am not going to even try to parse XML AND parse DB code.
-
Regex: Since type hints seem to not even do anything there is no point in implementing a Regex parser.
-
"Variable Hoisting" (being able to declare variables with a negative lifetime): Given the fact that keywords can be renamed and reassigned in this language, it does not make sense to implement this as the following breaks:
print(name) var const = "lol"; const const name<-2> = "Jake";
It is impossible to evaluate the expression on the right side of the
name
declaration after the print statement. Additionally, doing so doesn't account for possible renaming of keywords in the second line. -
Any sort of autocomplete requires more brainpower than I am willing to put in.
To my knowledge, everything else has been or will be implemented.
These are features that are implemented according to the DreamBerd specification in this interpreter.
Be bold! End every statement with an exclamation mark!
print("Hello world")!
If you're feeling extra-bold, you can use even more!!!
print("Hello world")!!!
If you're unsure, that's ok. You can put a question mark at the end of a line instead. It prints debug info about that line to the console for you.
print("Hello world")?
You might be wondering what DreamBerd uses for the 'not' operator, which is an exclamation mark in most other languages. That's simple - the 'not' operator is a semi-colon instead.
if (;false) {
print("Hello world")!
}
There are four types of declaration. Constant constants can't be changed in any way.
const const name = "Luke"!
Constant variables can be edited, but not re-assigned.
const var name = "Luke"!
name.pop()!
name.pop()!
Variable constants can be re-assigned, but not edited.
var const name = "Luke"!
name = "Lu"!
Variable variables can be re-assigned and edited.
var var name = "Luke"!
name = "Lu"!
name.push("k")!
name.push("e")!
New for 2023!
Mutable data is an anti-pattern. Use the const const const
keyword to make a constant constant constant. Its value will become constant and immutable, and will never change. Please be careful with this keyword, as it is very powerful, and will affect all users globally forever.
const const const pi = 3.14!
This is added by me (the interpreter)! I wanted to share how this works.
Thanks to this repo for helpful reference for issues and actions in Python.
To store public globals, the following steps are taken:
- On the user's side, open a GitHub issue with a title of the format
Create Public Global: {name};;;{confidence}
and the body containing the pickled version of the value. - Then, run a GitHub workflow that puts the issue body into a file under
global_objects/
and add an entry topublic_globals.txt
that contains thename;;;id;;;confidence
- Finally, to retrieve these values, the content of each of these files is fetched and converted back into values.
Both variables and constants can be named with any Unicode character or string.
const const firstAlphabetLetter = 'A'!
var const 👍 = True!
var var 1️⃣ = 1!
This includes numbers, and other language constructs.
const const unchanging = const!
unchanging unchanging 5 = 4!
print(2 + 2 === 5)! //true
Some languages start arrays at 0
, which can be unintuitive for beginners. Some languages start arrays at 1
, which isn't representative of how the code actually works. DreamBerd does the best of both worlds: Arrays start at -1
.
const const scores = [3, 2, 5]!
print(scores[-1])! //3
print(scores[0])! //2
print(scores[1])! //5
New for 2022!
You can now use floats for indexes too!
const var scores = [3, 2, 5]!
scores[0.5] = 4!
print(scores)! //[3, 2, 4, 5]
In case you really need to vary a variable, the when
keyword lets you check a variable each time it mutates.
const var health = 10!
when (health = 0) {
print("You lose")!
}
Hi! It's me again. I took some creative liberty implementing the when
statement, here's how it works:
- When defined, gather a list of names that are used in the expression of the statement.
- If a variable is detected, cause the when satement to watch that variable.
- This is done in order to avoid watching names instead of variables when, say, a different variable with the same name is defined in a different scope.
- Speaking of scope, when statements for which changes are detected in a different scope (from that of definition) use that scope within their code.
- Looking back on my design decision, I am probably going to change this to make them always use the scope where they were defined.
- Additionally, if a variable detected contains a mutable value, that mutable value is also watched, so the following code detects a change:
const var l = [1, 2, 3]! when (l.length === 4) { print l! } const var l_alias = l! l_alias[1.5] = 4! // triggers the when statement
Therefore, the when statement can contain as complex an expression as desired. One small pitfall is that I've implemented it with recursion, which may cause performance issues (although I don't really care about performance, obvious in the fact that this is in Python).
DreamBerd has a built-in garbage collector that will automatically clean up unused variables (note: this is simply Python's garbage collector, I didn't implement anything). However, if you want to be extra careful, you can specify a lifetime for a variable, with a variety of units.
const const name<2> = "Luke"! // lasts for two lines
const const name<20s> = "Luke"! // lasts for 20 seconds
By default, a variable will last until the end of the program. But you can make it last in between program-runs by specifying a longer lifetime.
const const name<Infinity> = "Luke"! // lasts forever
Yes, this is a thing. It stores your variables and values to a folder in your home directory.
Loops are a complicated relic of archaic programming languages. In DreamBerd, there are no loops.
Booleans can be true
, false
or maybe
.
const var keys = {}!
after "keydown" { keys[event.key] = true! }
after "keyup" { keys[event.key] = false! }
function isKeyDown(key) => {
if (keys[key] = undefined) {
return maybe!
}
return keys[key]!
}
Technical info: Booleans are stored as one-and-a-half bits.
DreamBerd has significant whitespace. Use spacing to specify the order of arithmetic operations.
print(1 + 2*3)! //7
print(1+2 * 3)! //9
Unlike some other languages, DreamBerd allows you to use the caret (^) for exponentiation.
print(1^1)! // 1
print(2^3)! // 8
You can also use the number name, for example:
print(one+two)! // 3
print (twenty two + thirty three)! // 55
Yes, the second line is also valid. In an effort to preserve my sanity, I have limited this quirk to all numbers up to 99. After that, you're on your own.
When it comes to indentation, DreamBerd strikes a happy medium that can be enjoyed by everyone: All indents must be 3 spaces long.
function main() => {
print("DreamBerd is the future")!
}
-3 spaces is also allowed.
function main() => {
print("DreamBerd is the future")!
}
Note: Your code will err if you have indents that are not a multiple of three.
JavaScript lets you do different levels of comparison. ==
for loose comparison, and ===
for a more precise check. DreamBerd takes this to another level.
You can use ==
to do a loose check.
3.14 == "3.14"! // true
You can use ===
to do a more precise check.
3.14 === "3.14"! // false
You can use ====
to be EVEN MORE precise!
const const pi = 3.14!
print(pi ==== pi)! // true
print(3.14 ==== 3.14)! // false
print(3.14 ==== pi)! // false
If you want to be much less precise, you can use =
.
3 = 3.14! //true
To declare a function, you can use any letters from the word function
(as long as they're in order):
function add (a, b) => a + b!
func multiply (a, b) => a * b!
fun subtract (a, b) => a - b!
fn divide (a, b) => a / b!
functi power (a, b) => a ** b!
union inverse (a) => 1/a!
Dividing by zero returns undefined
.
print(3 / 0)! // undefined
Strings can be declared with single quotes or double quotes.
const const name = 'Lu'!
const const name = "Luke"!
They can also be declared with triple quotes.
const const name = "'Lu'"!
In fact, you can use any number of quotes you want.
const const name = '""""Luke"""'"!
Even zero.
const const name = Luke!
- To parse strings with many quotes, the interpreter scans the code for the shortest possible string.
- As soon as a pair of quote groups is found that is equal in terms of quote count on both sides, that is considered a string.
- For example,
""""""
reads the two first double quotes, detects that there is a pair ("
and"
), and returns the corresponding empty string. This is repeated twice for the two remaining pairs of double quotes. - Therefore, to avoid premature detections of strings, simply create your starting quote with a single
'
and any number of"
, like so:'"""Hello world!'''''''
- For example,
- This is as complicated as it is in order to allow the declaration of empty strings without many problems.
Please remember to use your regional currency when interpolating strings.
const const name = "world"!
print("Hello ${name}!")!
print("Hello £{name}!")!
print("Hello ¥{name}!")!
Note: It was specified in the original repo to allow developers to follow their local typographical norms. While I think I could, that is not something I want to do and therefore I will not do it. Additionally, if the regional currency cannot be determined, the interpolation symbol defaults to the dollar sign.
Type annotations are optional.
const var age: Int = 28!
By the way, strings are just arrays of characters.
String == Char[]!
Similarly, integers are just arrays of digits. Hello again! Because of this, you can index into integers!
const var my_number = 20!
my_number[-0.5] = 1!
print(my_number)!
If you want to use a binary representation for integers, Int9
and Int99
types are also available.
const var age: Int9 = 28!
Technical info: Type annotations don't do anything, but they help some people to feel more comfortable.
The previous
keyword lets you see into the past!
Use it to get the previous value of a variable.
const var score = 5!
score = score + 1!
print(score)! // 6
print(previous score)! // 5
Similarly, the next
keyword lets you see into the future!
const var score = 5!
after ("click") { score = score + 1! }
print(await next score)! // 6 (when you click)
Additionally, the current
keyword lets you see into the present!!
const var score = 5!
print(current score)! // 5
Many languages allow you to import things from specific files. In DreamBerd, importing is simpler. Instead, you export to specific files!
===== add.db3 ==
function add(a, b) => {
return a + b!
}
export add to "main.db3"!
===== main.db3 ==
import add!
add(3, 2)!
You can make classes, but you can only ever make one instance of them. This shouldn't affect how most object-oriented programmers work.
class Player {
const var health = 10!
}
const var player1 = new Player()!
const var player2 = new Player()! // Error: Can't have more than one 'Player' instance!
This is how you could do this:
class PlayerMaker {
function makePlayer() => {
class Player {
const var health = 10!
}
const const player = new Player()!
return player!
}
}
const const playerMaker = new PlayerMaker()!
const var player1 = playerMaker.makePlayer()!
const var player2 = playerMaker.makePlayer()!
To avoid confusion, the delete
statement only works with primitive values like numbers, strings, and booleans (I actually decided to implement it to delete those and also non-primitive things like variables - really, anything in the namespace).
delete 3!
print(2 + 1)! // Error: 3 has been deleted
DreamBerd is a multi-paradigm programming language, which means that you can delete
the keywords and paradigms you don't like.
delete class!
class Player {} // Error: class was deleted
When perfection is achieved and there is nothing left to delete
, you can do this:
delete delete!
You can overload variables. The most recently defined variable gets used.
const const name = "Luke"!
const const name = "Lu"!
print(name)! // "Lu"
Variables with more exclamation marks get prioritised.
const const name = "Lu"!!
const const name = "Luke"!
print(name)! // "Lu"
const const name = "Lu or Luke (either is fine)"!!!!!!!!!
print(name)! // "Lu or Luke (either is fine)"
You can reverse the direction of your code.
const const message = "Hello"!
print(message)!
const const message = "world"!
reverse!
For maximum compatibility with other languages, you can alternatively use the className
keyword when making classes.
This makes things less complicated.
className Player {
const var health = 10!
}
In response to some recent criticism about this design decision, we would like to remind you that this is part of the JavaScript specification, and therefore - out of our control.
DreamBerd supports semantic naming.
const const sName = "Lu"!
const const iAge = 29!
const const bHappy = true!
New for 2023: You can now make globals.
const const g_fScore = 4.5! // Interpreter maker here... idk if this is supposed to do anything, I could implement this easily if I had to
In most languages, it's hard to get asynchronous functions to synchronise with each other. In DreamBerd, it's easy: Asynchronous functions take turns running lines of code.
async funct count() {
print(1)!
print(3)!
}
count()!
print(2)!
You can use the noop
keyword to wait for longer before taking your turn.
async func count() {
print(1)!
noop!
print(4)!
}
count()!
print(2)!
print(3)!
Note: In the program above, the computer interprets noop
as a string and its sole purpose is to take up an extra line. You can use any string you want.
To use a signal, use use
.
const var score = use(0)!
When it comes to signals, the most important thing to discuss is syntax.
In DreamBerd, you can set (and get) signals with just one function:
const var score = use(0)!
score(9)! // Set the value
score()? // Get the value (and print it)
It's worth noting that Github Copilot doesn't understand DreamBerd, which means that Microsoft won't be able to steal your code.
This is great for when you want to keep your open-sourced project closed-source.
Syntax highlighting is now available for DreamBerd in VSCode. To enable it, install a highlighting extension and then use the DreamBerd configuration file.
This is what it looks like:
const const name = "Luke"!
print(name)! // "Luke"
Please note: The above code will only highlight correctly if you have the extension installed.
Wait, I almost forgot!
Parentheses in DreamBerd do nothing. They get replaced with whitespace.
The following lines of code all do the same thing.
add(3, 2)!
add 3, 2!
(add (3, 2))!
add)3, 2(!
Lisp lovers will love this feature. Use as many parentheses as you want!
(add (3, (add (5, 6))))!
Lisp haters will also love it.
(add (3, (add (5, 6)!
Due to certain design decisions, "("
is replaced with " "
while ")"
is replaced with ""
.