BTB Language

Better Than Batch (BTB) is a language and compiler meant for easier development with a complete standard library and cross platform goals. Commonly used for everyday cross-platform intermediate scripting needs when Bash and Batch isn't enough and Python is too slow.

Download BTB Friendly guide

Latest version: 0.2.0 (2024-10-14)

Changelog
#import "OS" #import "Threads" #import "Logger" #macro WORK_COUNT 5 global global_number: i32 threads: Thread[3] ids: i32[]{1,2,3} for @ptr threads { *it = ThreadCreate(work, &ids.ptr[nr]) } log("Main thread is waiting...") for @ptr threads it.join() log("All ", threads.len, " threads finished") fn @oscall work(arg: void*) -> i32 { global print_mutex: Mutex id := *cast<i32*>arg for 1..WORK_COUNT+1 { // keeping the access to global number outside // of lock to show off data race condition result := global_number += nr print_mutex.lock() log(" Thread ", id,": result ", result) print_mutex.unlock() ProcessSleep(0.005) // 5 ms } return 0 }

About the language

Yoo! Why should you use this language? Because you may find it fun and refreshing. It's designed with the developer in mind, the fast comptile time, circular imports, control at assembly level, data-oriented focus, and the best of all built in metaprogramming capabilities. Well, metaprogramming is still in the works. Global variables are evaluated in a virtual machine at compile time, we're slowly getting there. Now some warnings. The language is developed by one person. I have a job and can only work on the language for a couple of hours every day. It is slowly coming together at a steady pace (last thing I did was experimenting with ARM code generation). There are many bugs in the compiler and the standard library is incomplete and will go through many changes. This will stay true until version 1.0.
image...

For loops, slices, and ranges

Of course the language supports for-loops but the syntax is very convenient in combination with slices and ranges. You can even implement your own iterators for your own container data structures like arrays or maps (see docs).

i
#import "Logger" for 0..5 { log("index: ", nr) } numbers: i32[]{ 3, 5, 7, 9 } for numbers log("num: ", nr, " ", numbers[nr])

Runtime type information

The language has type information at runtime which allows you to print any struct since you know the members' name, offset, size, and data type. Taking this one step further, if you have a pointer and it's type then you can recursively print the data if it's a primitive type or if a struct, recursively print all the members.

#import "Logger" #import "String" #import "Array" struct Box { x: float; str: char[]; builder: StringBuilder; arr: Array<i32> } box: Box box.x = 5.8 box.str = "Hello!" builder.append("Goodbye") arr.add(23) arr.add(52) log("This is the box: ", &box)

Compile-time Execution for your metaprogramming needs!

Pre-calculate global data, read and set up configuration files, create your own build system that runs at compile time, and automate repetitive tasks or scripts. These are just a few examples of what this feature can do.

It works likes this: global variables are always evaluated at compile time and the #run directive can be placed anywhere to execute any code, even if it calls functions from the operating system or other dynamic library! You can also use #run as an expression where it's final value is inserted as a literal in the program.

The program on the side reads and increments a build version number from a file and includes it into the program. It also writes back the new version number to the file which means that every time you compile the program, each executable will get it's own build number!

#import "File" #import "String" #import "Memory" global BUILD_VERSION: i32 = get_version(); fn main() { log("Build Version: ", BUILD_VERSION); } fn get_version() -> i32 { file_size: i64 file := FileOpen("version.txt", FILE_READ_AND_WRITE, &file_size); if !file return 0 buffer: char[32] if file_size < buffer.len buffer.len = file_size FileRead(file, buffer.ptr, buffer.len) version := parse_i64(buffer); text: StringBuilder text.append(version + 1) text.append("\n") FileSetHead(file, 0) FileWrite(file, text.ptr, text.len) FileClose(file) return version; }

Inline assembly

The language supports convenient inline assembly written using intel syntax just like you would in a normal assembly file. Assembly is "pasted" between your other code in expressions or statements. The assembly block allows for input and output expressions from and to the assembly.

#import "Logger" value := asm<i32>(9,10) { pop rbx // 10 pop rax // 9 add rax, rbx // 19 = 9 + 10 push rax // 19 } log("value: ", value)

Polymorphism

Some polymorphism (templates, generics) for your generic array and map needs. Works for structs and functions.

struct Box<T> { items: T[4]; used_items: i32; fn set(index: i32, value: T) { items[index] = value if index >= used_items used_items = index+1 } } box: Box<char[]> box.set(0, "Hello") box.set(2, "there") log("Items: ",box.used_items) for 0..4 { log(" ",nr, ": ",box.items[nr]) }
Join the Discord community!