Bingus is a tiny compiled language with simple syntax. This project is my playground for learning compilers — parsing code, building ASTs, and generating machine code. Just a fun way to explore how programming languages work under the hood.
Bingus is a small compiled language with the following implemented features:
- Supports integer literals.
- Example:
let x = 5;
- Variables can be declared using
let. - Variables are stored on the stack and have proper scoping.
- Variables can be reassigned using
=. - Example:
let x = 8;
x = x + 2;- Addition:
+ - Subtraction:
- - Multiplication:
* - Integer Division:
/ - Example:
let add = x + y;
let sub = x - y;
let mul = x * y;
let div = x / y;- Can print integer literals or variables to the terminal.
- Syntax:
print <number or variable> - Example:
print x;
print 42;- Boolean constants:
true(1),false(0) - Supported comparators:
==,<=,>=,<,> - Example:
let x = 3 <= 6;
print x; // prints 1 (true)- Can return a value at the end of the program.
- Automatically adds
return 0if missing. - Example:
return x;- Syntax:
if (<condition>) {
// then block
} else {
// else block
}- Condition must evaluate to boolean (0 or 1).
- Syntax:
while (<condition>) {
// loop body
}- Proper variable scoping and assignment inside loops.
- Example:
let i = 1;
while (i <= 100) {
print i;
i = i + 1;
}- Single-line comments:
// ... - Multi-line comments:
/* ... */ - Example:
// This is a single-line comment
/* This is
a multi-line comment */- Automatic stack allocation for variables.
- Variables are scoped properly for nested blocks.
- Supports reassigning variables in current or outer scopes.
This is the current implemented feature set for Bingus as of November 2025.
To compile a .bng file, can follow these steps:
Create a new file bingus ending with the .bng extension, or just use the test.bng file.
Now, build the project using the configured Makefile by running:
make build LINUX=1This builds an exacutable for the linux amd64 architecture, because the project will use assembly x86 and will be running on a docker image with that specific architecture.
if you want to run it without docker, you can also build it locally with:
make buildThis simply builds the executable to your current architecture.
Start the docker container by running the following command:
docker compose run --rm bingus-dev This will give you a linux shell with the necessary tools like nasm and ld, as well as makes sure you use the correct architecture. This will give you the ability to run the executable that is generated by the compiler.
To compile the file, run this command in the shell:
./bin/bingus <your-filename>.bngThis will compile your bingus file if it is correct. Otherwise, it gives you an error.
To run the compiled .bng file, you can run the created executable in the output folder like so:
./output/testThis will show you the resulting code execution, if there is anything to show.
It can now compile a simple return statement with a return code into assembly and then machine code. It runs on a docker image for a linux x86 distro, generates the given code in a .bng file into assembly x86. Then it uses nasm to create the object file and finally it uses the linker command ld to create the machine code.
It can now store and retrieve variables, aswell as do arithmetic operations with numbers and variables🥳. You define a variable using the syntax let <variable> = <value>. It currently only supports integers and number literals. The variable can be any string.
To do arithmetic, you chose to do addition (+), subtraction (-), multiplication (*), and integer division (/).
Examples for valid programs would be:
# Defining variables
let x = 8;
let y = 2;
# Arithmetic operations using variables
let add = x+y;
let sub = x-y;
let mul = x*y;
let div = x/y;
# Returning variables
return add+sub+mul+div;The return statement will evaluate the result to be 36 and will set that as the exit code. You can verify this by compiling the code, running the compiled code and then by checking the exit code of the compiled program with echo $?.
This syntax is temporary and will most likely be changed to something else, but I have still not really decided exactly what I want it to be.
The language now has print features. It can now print numbers literals and variables to the terminal using the syntax print <number or variable>. This makes it way more usable as you now do not need to check the return code to see the result of the written code, because you can now just print directly to the terminal.
Some example code, building on the last example, that compiles and works is:
The language now has boolean literals and can do boolean comparisons that result in boolean literals. An example that compiles is:
let x = 3 <= 6;
print x;
return 0;It supports ==, <=, >=, < and > as comparators and also true and false as boolean constants. If the literal is true it will result in the integer value 1 and 0 otherwise.
let x = 8;
let y = 2;
let add = x+y;
let sub = x-y;
let mul = x*y;
let div = x/y;
let all = add+sub+mul+div;
print all;It also now checks if there is a return statement at the end of the file, and if there is not, then it prints a warning at compile time and adds a default return 0 to the generated assembly code. This is was to make the syntax a bit simpler and also to avoid segmentation fault when a user does not add a return statement to the code file.
Now it also has slightly better stack space management by allocating the appropriate space when storing variables on the stack, and as well as now not pushing and popping of the stack for every binary operation. Instead it simply writes the right side and left side to each of their respective registers.
The language now officially supports if-else statements. The syntax looks like so:
if (3>5) {
print 3;
}
else {
print 4;
}
return 0;As you can see, it takes a condition of type boolean, represented as either 0 if false and 1 if true, and then depending on if the condition is true or false, it runs the correct conditional block. In the example above, the condition is false, therefore it evaluates to be 0 and then the code in the else block runs. If the condition was (3<5), then the first block would run. This is a big step for the language to actually begin to be useful and somewhat practical.
The language now officially supports while loops, comments and also has proper variable scoping🥳. The syntax for an example loop looks like so:
let i = 1;
while(i<=100){
print i;
i = i+1;
}
return 0;This simple program prints all the numbers from 1 to 100. It also has proper variable assignment now that allows it to re-assign variables that have already been declared in the current scope or an outer scope. This allows for the c-like syntax of i=i+1.
I also added the simple ability to add one-line and multi-line comments in the code. The syntax for that is simply // for one-line comments and /* ... */ for multi-line comments. An example of comments in use could be:
/*
Square printer
This program prints all the squares from 1 to 100
*/
let i = 1;
while(i<=100){
print i*i; // print the square
i = i+1; // next number
}
return 0;The next I would like to implement is else if, % operator, strings, functions, and finally arrays. The final goal is to make it Turing Complete, but temporary goal is to be able to write a simple FizzBuzz program. For that to be possible, I would need to add modulo and as a minimum, and then else if would also be very helpful, technically not strictly necessary.