Documentation - andre-1337/loxpp GitHub Wiki

Documentation

This is where you can find somewhat in-depth documentation for Lox++. Please keep in mind it may be incomplete or not updated.

The basics

Lox++ is a very easy language to learn. Main difference is that every .lox file must have an entry point, that should have the signature fn main(argc, argv). The interpreter enforces this signature and the existence of this method. The REPL does not need any main functions, it simply executes the code you give it. You can also give the .loxlib extension to files, and they do not need any entry points, although this is only recommended if you're actually developing a library for Lox++.

Pattern matching

Pattern matching is a very useful feature. Despite being implemented in Lox++, it's still not complete, as it is missing the ability to handle cases with array or object destructuring. It can, however, still be used.

enum Days {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}

fn day_to_string(day) {
    match day {
        case Days.Monday -> return "Monday";
        case Days.Tuesday -> return "Tuesday";
        case Days.Wednesday -> return "Wednesday";
        case Days.Thursday -> return "Thursday";
        case Days.Friday -> return "Friday";
        case Days.Saturday -> return "Saturday";
        case Days.Sunday -> return "Sunday";
    }
}

println(day_to_string(Days.Thursday)); // "Thursday"

In this case, the match inside the day_to_string function is going to match the correct case and then return the string representation. Note that you can use match for several other things, like matching strings, numbers and other types of expressions.

Dictionaries

Also known as object literals in languages such as JavaScript.

let Address = { address: { city: "New York", zip: "00000" } };
let Person = { name: "John", age: 71, ...Address };

println(Person); // { address: { zip: 00000, city: New York }, name: John, age: 71 }
println(Person.address); // { address: { city: "New York", zip: "00000" } }
println(Person.name); // John
println(Person.age); // 71

In this case, we are defining an Address object. It contains the living city and zipcode of John. Then, we define the Person object, and spread the Address object on it. This is going to create a single object out of both. Object keys are defined just like identifiers, there are no quotes or anything. Values can be any type of expression.

Arrays

Arrays work just like in any other language. There are also methods you can use on arrays for several things, like getting an item from a certain index (an alternative to indexing), inserting items, popping items, removing items at a certain index, getting the length of the array, shuffling the array, checking if it is empty, deleting all the items in the array, mapping, filtering and reducing.

let number_array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
number_array.shuffle();

println(number_array); // [ 5, 8, 9, 6, 1, 3, 2, 4, 7 ]
println(number_array[3]); // 6
number_array[6] = 69;
println(number_array); // [ 5, 8, 9, 6, 1, 3, 69, 4, 7 ]

Nothing much to add here.

Tuples

Yes, Lox++ has tuples. They also work just like they do in any other language that implements tuples, like Rust, for example.

let tuple = ("John", "71");
println(tuple.0); // "John"
println(tuple.1); // 71

Nothing much to add here either.

Throwable errors

Lox++ implements throwable errors. These take advantage of the fact that the interpreter actually uses the traits defined in the language to provide functionality. There is a Throwable trait, used to provide functionality to classes that can be used to alongside the throw keyword.

In the standard library, there is already an Error class that users can make use of, but if you want to define your own custom error types, the functionality is also there.

throw Error("Something went terribly wrong ):"); // RuntimeError: Something went terribly wrong ):

class MyCustomErrorHandler with Throwable {
    fn init(msg) {
        self.msg = msg;
    }

    fn message() -> self.msg;
}

throw MyCustomErrorHandler("This is an error thrown by my custom error handler!");

Consider that the implementation of Throwable is as follows:

trait Throwable {
    abstract fn message();
    fn type() -> "Throwable";
}

The message method MUST always be implemented. If you don't, the interpreter will throw an error about it. The type method is optional, you can implement it if you want, otherwise the "Throwable" string will carry throughout all the defined error handlers.

Try/Catch

Complimenting the previous feature, there is also try/catch. This allows you to catch errors and do whatever you want with them.

class DoSomething {
    fn init() {}

    fn do_something() {
        throw Error("what did you expect this method to do?");
    }
}

try {
    DoSomething().do_something();
} catch error {
    println("Something went wrong: " + error); // "Something went wrong: what did you expect this method to do?"
}

This is a very useful feature to safely catch errors in your programs.

Traits

As shown before, there are traits in Lox++. This is a very useful feature for writing reusable code. Traits cannot be constructed. There are also abstract methods that MUST be implemented, as seen in the previous example. You can also refer to Throwable errors to see how to define traits.

Class header initializers

There are also class header initializers. A very useful feature if you want to define a class that has an initializer in a very quick and easy way. For example, the following class is pulled out of the standard library:

class Pair(key, value);

This is shorthand syntax that under the hood means this:

class Pair {
    fn init(key, value) {
        self.key = key;
        self.value = value;
    }
}

You can also use this syntax alongside with inheritance and even define a body with more methods if you want to.

Namespaces


Enumerators


Lazily evaluated expressions


Static methods


For-in loops


Lambda expressions


Overloadable operator methods


The standard library