Brick Lang is a compiled programming language inspired by C and aims to one day be compiled in it self. My original inspiration came from Pixeled on YouTube on which this code is based, but largely expanded. (GitHub of the inspiration)
For both variants:
- You need
g++installed that supports c++ up toc++17
For Windows:
- You need
masm32installed on your computer underC:\masm32.
For Linux:
- You need to have installed
nasmfor assembling andldfor linking
-
On Linux you can just run
makewith the makefile -
On Windows you need to run the command:
g++ -std=c++17 -O2 src/preprocessor.cpp src/tokenization.cpp src/parsing.cpp src/generation.cpp -o brick.exe
-
To compile your .brick file just run
brick <pathname> [flags]Note: It is important that the pathname is the first supplied argument
-
Optional compilerflags include
-infofor compilation information and-nomicrosofton Windows to get rid of the Microsoft Logo things when using masm32 -
You can also add the
-O1or-O2flag. The-O1flag removes unused functions from the assembly and the-O2flag does some slight optimizations.
Note: This is currently only possible on Linux
-
Then just run the file with
./<filename>on Linux or.\filename.exeon WindowsNote: if you want to be able to access the compiler, just add
export PATH="$PATH:<\path-to-the-brick-compiler>/Brick-Lang"to the end of ~/.bashrc on Linux
I am currently working on creating a vscode extention to highlight Brick-Lang keywords, currently you can install brick-lang-highlighter-0.0.2.vsix found in the syntax-highlighter directory in vscode to get some simple highlighting.
If you have some complaints about the highlighting, take those up with the Creator
Like in C, your program will start executing at a main function:
brick main{
exit 0;
}
the brick keyword is hereby the universal function keyword
By using exit <exitcode> you can exit the program from any functionscope;
Comments
Code comments are written like this:
//this comments out the rest of the line
/*
This is a multiline comment
Yay
*/
Variables
You can declare Variables as follows:
dec <variable name> as <varable type> -> <value> ;
Valid variable types are int (4 bytes), short (2 bytes), byte (1 byte) and bool (still 1 byte but can only have value 0 or 1)
So a valid declaration would look like:
dec my_var as int -> 42;
Note : booleans can only be initalized with true, false or null
Variables can also be declared as immutable by adding the const keyword after the value
Assignment of variables looks like this:
set <variable name> to <new value> ;
e.g. :
set x to (5 * 2) + 1;
Variables can also be incemented or decremented:
my_var++;
my_var--;
Or changed by an expression like this:
my_var += 5 * 2;
my_var /= 10;
Global variables
Even though global variables might be considered bad pactise, Brick-Lang opts for giving the programmer as much freedom as realistically possible.
Therefore global variables are declared like this:
globals : [
<variable declaration>
...
]
You can declare your global variables inside of this globals scope and access them like every other variable by adding globals:: before the variable name
Example:
globals : [
dec x as int -> 42;
]
brick main{
set globals::x to 13;
dec y as int -> globals::x;
}
Arrays
To define an array of variables, just write:
dec <array_name> as array <variable type> -> <size of array>;
Note here that initialization of arrays with values is not directly possible.
To assign a value to an array index, write:
set my_array[<index>] to <value>;
The index can be a number literal or a variable
To get the value of an array index, you can just use my_array[<index>] in almost any expression.
Strings
Due to the development timeline of this Language, there are two different ways to create strings:
dec my_str_1 as string -> "Variant 1";
This variant is basically just a string constant, which is not mutable and is only really used to be written to an output.
The second variant is the C-Style way of initializing strings.
dec my_str_2 as array byte -> 10 {"Variant 2"};
This just initializes an array of bytes with the individual characters.
Note: This feature is only available on Linux, because I only added it recently and don't want to implement every new feature twice... If you are using Windows, just use wsl or something and spin up an Ubuntu VM
Note 1: It is important to always allocate one more byte for the null terminator, which gets automatically added by the compiler
This second variant allows for indexing of the string and manipulating it.
set my_str_2[0] to 'v';
This variant is also the one used when passing strings as arguments.
String Buffers
String buffers are basically a constant block of memory, which can only be used to write a string to:
dec buf as strbuf -> 32; //32 reserved bytes
set buf to "This is a string";
set buf to my_str_1;
They were added before actually using byte arrays on the stack, so it is no longer good practise to use them.
Pointers
Pointers are essential when dealing with low-level byte manipulation or managing memory, in Brick-Lang pointers are declared as follows:
dec my_ptr as <var type to point to> ptr -> <value>;
The value of a pointer can be either null or the address of a variable with matching type, which can be accessed using the & operator:
dec my_ptr as int ptr -> &my_var;
To get the value of the variable that the pointer is pointing to, you need to dereference the pointer using the $ operator:
dec x as int -> $my_ptr;
Control statements
Brick-Lang supports common control statements, such as if, for and while
If Statements look like this
if my_var > 0{
...
}else{
...
}
There is no else if at the moment because I do not view them as necessary
For loops look like this:
for dec i as int -> 0; i < 6; i++;{
...
}
And while loops look like this:
while my_var != 0 {
...
}
You can use break in while or for loops to exit the loop early.
You can use next to directly jump to the next iteration of the loop.
Supported comparison operators are :
== (equal)
!= (no equal)
< (less than)
<= (less or equal than)
> (larger than)
>= (larger or equal than)
These can be chained together by using and and or:
if my_var < 0 and null == false{
...
}
Including other .brick files
To include another file, you can simply write
#include <pathname>
This is a copy-cut-paste operation, meaning the code of the other file will simply be added to the current file (except for the main function, that gets cut out)
It is important that the pathname is literally just the name without any symbols around it. If I wanted to include the file operation file from the standard library, I would just write:
#include stdlib/fileops.brick
Note: The filename is read until the end of the line
Preprocessor defines
Similar to how you can define preprocessor replacements, you can write the following:
#define <name> <expr>
this could be:
#define G_PI 3
This replaces all instances, which do not occur in variable names, of the <name> with the <expr> in the code.
Note: There is no restrictions on what words can be replaced, meaning that it is totally legal to define true as false
Functions
A function in Brick-Lang is declared using the brick keyword.
Up to 4 Arguments can be provided after a colon by doing <type> -> <var name>
Return types are added after the closing curly braces like this: }=> <type>
Note: Return types act rather as a hint to the developer and not as a hinderance to the actual returned value
Function with no arguments:
brick my_func_one{
...
}=> int
Function with arguments:
brick my_func_two : byte ptr -> arg_1, int -> arg_2{
...
}=>byte ptr
you can return from a function using the return keyword with a value
brick my_func_three : int -> x{
return x * x;
}=> int
Note: if you want to return early but have nothing to return, you can just return null or any other value
You can use the function call as part of an expression:
dec x as int -> 2;
dec z as int -> my_func_three(x);
Structs
Structs are simply a collection of variables, that can be accessed with the . operator:
Note: Structs can only be declared outside of a function scope
structs my_struct_one{
dec z as int -> 0;
}
struct my_struct{
dec x as int -> 0;
dec y as int -> 0;
dec arr as array byte -> 32;
dec other_struct as my_struct_one;
}
brick main{
dec s as my_struct;
set s.my_struct.z to 2;
exit 0;
}
Input and Output
You can write input from the standard console input into either a string buffer or an array of bytes like this:
input my_str_buf;
input my_byte_arr;
Note: just like in C, writing to byte arrays is prone to buffer overflows
You can output strings directly like this:
output "Hello World!";
This will print a newline automatically, if you don't want a newline just add a noend after the last argument.
To output variables you first need to convert them to their ascii representation:
#include stdlib/int_to_ascii.brick
brick main{
dec x as int -> 42;
output "Value of x: ", itoa(x);
exit 0;
}
You can also output stringbuffers like this, as long as they have a value:
output my_str_buf;
Inline Assembly
Inline assembly is considered bad practise, but needed to write wrappers for system calls.
I advise you to not use it outside of adding to the stdlib
Bitwise Operations
You can use the bitwise operations and, xor and or, which are denoted with & , ^ and | respectively:
dec my_var as int -> 3 | 4; // 0b011 | 0b100 = 0b111 = 7
set my_var to my_var & 5; // 0b111 & 0b101 = 0b101 = 5
set my_var to myvar ^ 1; // 0b101 ^ 0b001 = 0b100 = 4
There are also bit shifting operators:
set my_var to my_var << 3; // Shift left by 3, same as multiplying by 8
set my_var to my_var >> 3; // Shift right by 3, same as dividing by 8
The bitwise operation not is denoted with a leading ~:
set my_var to ~my_var; // if my_var was 0000 0001 before, it is now 1111 1110
Struct Pointers
Pointers to structs can be defined as the following:
dec <struct pointer variable name> as <struct name> ptr -> <value>;
As with all pointers, the value van either be null(0) or a reference to a struct with the corresponding type
They can also be declared inside of structs, making it possible to access other structs from a struct by reference.
You can access the elements of the struct that your struct pointer is pointing to, by simply using the . operator, as you would if the struct pointer was a regular struct
Note: this is a feature and definitely not me being too incompetent to parse that
You can also define a struct pointer to the struct itself, allowing for linked lists:
struct node{
dec item as node ptr -> null;
dec data as int -> 0;
}
If you want to see more code about struct pointers with some small unit tests, view examples/struct_pointers.brick
Console Arguments
You can access console arguments (which might be known as argc and argv in C) by just writing two variable names after the main function.
The first variable will be assumed to be the argument count and the second one is the pointer to pointers of the arguments:
brick main : argc, argv{
}
These can then be used as regular variables
Note: argc is treated as an integer and argv as an int ptr
If you have any questions left, you can look at the example code provided in examples/
If you feel, that there is any functionality missing in the standard library, open a pull request and write it yourself