Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.
/ Banek Public archive

Toolchain for custom programming language that combines best from popular languages

Notifications You must be signed in to change notification settings

djordje200179/Banek

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Banek

The programming language of the future. It is a dynamically typed language with a syntax mixing the best of Rust, Go, JS, Python and C.

The language is still in development and is not ready for production use. It was made as a hobby project, and it will be updated whenever I have time and motivation to add new features.

Toolchain

Every part of the toolchain is written in Go. So my language depends on Go runtime and its standard library.

Lexing and parsing is done completely by hand. I did not use any external libraries for this, because I wanted to learn how it works. Both the lexer and the parser run on their own goroutines, so they can work in parallel. They work in a streaming fashion, producing tokens and sending them to the consumers via buffered channels.

Parsing is done using a recursive descent Pratt parser. It is a top-down parser that uses operator precedence to parse expressions. It is very simple to implement, and it is also very fast.

Interpreter

The interpreter is used for running Banek programs without compiling them or for running the REPL. Executing a program with the interpreter is slower than executing the bytecode because it recursively evaluates the AST and parses nodes on every step.

Compiler

The compiler is used for compiling Banek programs to bytecode. The bytecode can then be run by the emulator. It does not perform any optimizations yet.

Emulator

The emulator is used for running Banek bytecode. It is pretty fast, but the bottleneck is the Go runtime.

Disassembler

The disassembler is used for disassembling Banek bytecode. It is useful for inspecting the generated bytecode.

Features

Variables

Types

Banek is a dynamically typed language, meaning that the type of variable is not known until runtime. The following types are supported:

  • booleans
  • integers
  • strings
  • arrays
  • functions
  • undefined constant
let num = 1;
let s = "Hello, world!";
let arr = [1, 2, 3];
let fun = || -> 1;

Mutability

Variables are by default immutable. This means that they cannot be reassigned after they are declared. To make a variable mutable, the keyword mut must be used before the variable name.

let a = 1;
let mut b = 2;

a = 3; // Error
b = 4; // OK

Visibility

Variables have block scope. This means that variables declared inside a block are not accessible outside of that block.

let a = 1;
{
    let b = 2;
}
// b is not accessible here

Functions

Built-in functions

Few functions are built-in to the language. These are needed for interacting with the outside world and for more complex operations.

  • print - Prints given values to the standard output
  • println - Prints given values to the standard output and adds a newline
  • read - Reads a value from the standard input and returns it as a string
  • readln - Reads a line from the standard input and returns it as a string
  • len - Returns the length of the given array
  • str - Converts the given value to a string
  • int - Converts the given value to an integer
print("Hello, world!");
print(len([1, 2, 3])); // Prints 3

Visibility

Functions are declared either by statements or as lambda expressions.

When declared as a statement, keyword func and the name of the function should be given, followed by the arguments in parentheses and the body of the function in curly brackets.

When declared as a lambda expression, vertical bars are used instead of parentheses around the arguments, after which an arrow is used to separate the arguments from the body. The body can be only one expression, and it is implicitly returned.

func foo() {
    return 1;
}

let doubler = |num| -> num * 2;

Closure

Functions can access and capture variables from the outer scope.

func adder(increment) {
    return |num| -> num + increment;
}

let addOne = adder(1);
print(addOne(1)); // Prints 2

Values

Functions are first-class citizens, meaning that they can be passed as any other value.

func getter() {
    return 1;
}

func foo(getter) {
    print(getter());
}

Arguments

Functions can take any number of arguments. Arguments are declared inside parentheses after the function name.

If a function is called with too few arguments, the missing arguments are set to undefined. If a function is called with too many arguments, the extra arguments are ignored.

func foo(a, b) {
    return a + b;
}

Operators

Arithmetic operators

All the basic arithmetic operators are supported. They not only work with numbers, but some of them also work with strings and arrays.

  • + - Addition or concatenation
  • - - Subtraction
  • * - Multiplication
  • / - Division
  • % - Remainder
  • ^ - Exponentiation
print("Hello, " + "world!");    // Prints "Hello, world!"
print([1, 2] + [3, 4]);         // Prints [1, 2, 3, 4]
print(5 ^ 2);                   // Prints 25

Comparison operators

Comparison operators are used to compare two values. They return a boolean value depending on the result of the comparison.

Every value can be compared to every other value for equality. However, only integers and strings can be compared for ordering.

print(1 == 2);      // Prints false
print(1 != "1");    // Prints true
print(1 < 2);       // Prints true

Special operators

There are also some special operators that are used for more complex operations. Example of such operator is a left arrow operator (<-), which is used for inserting value into an array without creating a new array every time.

let arr = [];
arr <- 1;
arr <- 2;
print(arr); // Prints [1, 2]

This operator also serves as unary operator for popping first element from an array.

let arr = [1, 2, 3];
print(<-arr); // Prints 1
print(arr);   // Prints [2, 3]

Control flow

If statements

If statements are used to execute a block of code conditionally, like in most other languages.

Brackets are not needed around the condition. Bodies of consequent and alternative branches do not need to be blocks if they consist of only one statement.

if 1 == 2 then
    print("1 is equal to 2");
else
    print("1 is not equal to 2");

Also, these statements can be used as expressions. In this case, the alternative branch is mandatory, and the value of the expression is the value of the executed branch.

let a = if 1 == 2 then "same" else "different";

While loops

While loops are used to execute a block of code repeatedly until a condition is met.

Like if statements, brackets are not needed around the condition and the body does not need to be a block if it consists of only one statement.

let mut i = 0;
while i < 10 do {
    print(i);
    i = i + 1;
}

Comments

Currently, only single-line comments are supported. They start with # and continue until the end of the line.

let a = 1;
# let b = 2;  <- This line is commented out
let c = 3;

About

Toolchain for custom programming language that combines best from popular languages

Topics

Resources

Stars

Watchers

Forks

Languages