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