rebo

Rebo is an expression-based scripting language with syntax heavily inspired by rust. It is statically typed and has backwards type inference. When executing a script, rebo fully analyses and checks the source code including all types, before it runs. While that approach is usually used for AOT (Ahead Of Time compilers), the execution of rebo is interpreted and thus embeddable on all systems. This approach also allows defining types and functions after their first use.

Samples

A few samples to see rebo in action.

fibonacci

Recursive:

#![allow(unused)]
fn main() {
fn fib_rec(i: int) -> int {
    match i {
        0 => 0,
        1 => 1,
        i => fib_rec(i - 1) + fib_rec(i - 2),
    }
}
assert_eq(fib_rec(6), 8);
}

Iterative:

#![allow(unused)]
fn main() {
fn fib_iter(mut i: int) -> int {
    if i <= 1 {
        return i;
    }
    let mut a = 0;
    let mut b = 1;
    i -= 2;
    while i >= 0 {
        let temp = b;
        b += a;
        a = temp;
        i -= 1;
    }
    b
}
assert_eq(fib_iter(90), 2880067194370816120);
}

Merge Sort

#![allow(unused)]
fn main() {
fn merge_sort<T>(array: List<T>) -> List<T> {
    merge_sort_internal(array, 0, array.len())
}

fn merge_sort_internal<T>(array: List<T>, start: int, end: int) -> List<T> {
    let len = end - start;
    if len == 1 {
        return List::of(array.get(start).unwrap());
    }
    
    // split
    
    let middle = start + len/2;
    let left = merge_sort_internal(array, start, middle);
    let right = merge_sort_internal(array, middle, end);
    let sorted = List::new();
    let mut left_index = 0;
    let mut right_index = 0;
    
    // merge
    
    while left_index < left.len() && right_index < right.len() {
        let left = left.get(left_index).unwrap();
        let right = right.get(right_index).unwrap();
        if left <= right {
            sorted.push(left);
            left_index += 1;
        } else {
            sorted.push(right);
            right_index += 1;
        }
    }
    
    while left_index < left.len() {
        sorted.push(left.get(left_index).unwrap());
        left_index += 1;
    }
    while right_index < right.len() {
        sorted.push(right.get(right_index).unwrap());
        right_index += 1;
    }
    
    sorted
}

let array = List::of(5, 4, 7, 2, 1, 3, 2);
let sorted = merge_sort(array);
assert_eq(sorted, List::of(1, 2, 2, 3, 4, 5, 7));
}

Features

See the features of rebo in action with code examples.

Hello World

#![allow(unused)]
fn main() {
print("Hello, World!");
}

Expressions

Everything in rebo is an expression.

#![allow(unused)]
fn main() {
let a = 0;
let mut a = a + 1;
let mut c = add_one(a);
c += {
    let c = 1337;
    c + 42
};
assert(c == 1381);
}

Math and Numbers

#![allow(unused)]
fn main() {
assert_eq(1 + 2 * 3, 7);
assert_eq(5 - -5 - 10, 0);
assert_eq(0b1 + 0x2 * 3 * 4, 25);
assert_eq(1 ^ 3, 2);
assert_eq(5 % 2, 1);
assert_eq(float::to_bits(0.0), 0);
assert_eq(float::from_bits(0x4060B66666666666), 133.7);

// parse numbers
let a = "1337";
let b = "42.";
let c = "abc";
let d = "-1337";
let e = "-42.";
assert(a.parse_int() == Result::Ok(1337));
assert(b.parse_int() == Result::Err(()));
assert(c.parse_int() == Result::Err(()));
assert(d.parse_int() == Result::Ok(-1337));
assert(e.parse_int() == Result::Err(()));
assert(a.parse_float() == Result::Ok(1337.));
assert(b.parse_float() == Result::Ok(42.));
assert(c.parse_float() == Result::Err(()));
assert(d.parse_float() == Result::Ok(-1337.));
assert(e.parse_float() == Result::Ok(-42.));
}

Control Flow

#![allow(unused)]
fn main() {
// if - else if - else
assert(if true { 1337 } else { panic("F") } + 5 == 1342);


// match
assert(match 1 {
    0 => 21,
    1 => 1337,
    _ => 42,
} == 1337);

let res = match 1 {
    0 => "it was a 0",
    foo => f"it was something else: {foo}",
};
assert_eq(res, "it was something else: 1");

}

Loop, While, For

#![allow(unused)]
fn main() {
// loop
let val = loop {
    break "loop";
};
assert_eq(val, "loop");

// loop labels
let val = 'outer: loop {
    loop {
        break 'outer "outer loop"
    }
};
assert_eq(val, "outer loop");

// while
let mut i = 0;
while i < 3 {
    i += 1;
}
assert_eq(i, 3);

// while with labels
let mut i = 0;
'outer: while true {
    while true {
        i += 1;
        break 'outer;
    }
};
assert_eq(i, 1);

// for loop
let list = List::of(1337, 21);
let mut sum = 0;

for i in list {
    sum += i;
}
assert(sum == 1358);

let mut sum = 0;
'outer: for i in list {
    for i in list {
        sum += i;
        break 'outer;
    }
}
assert_eq(sum, 1337);
}

Functions

#![allow(unused)]
fn main() {
// functions can be used before their definition
assert(foo(10, 20) == 60);

fn foo(mut x: int, mut y: int) -> int {
    x += 10;
    y += 20;
    x + y
}

fn choose(cond: bool, a: int, b: int) -> int {
    if cond {
        return a;
    }
    b
}

assert_eq(choose(true, 1, 2), 1);
assert_eq(choose(false, 1, 2), 2);
}

Advanced

#![allow(unused)]
fn main() {
// functions as first-class citizens
let new: fn<T>() -> List<T> = List::new;
let test = assert;
test(f"{new()}" == "[]");
// anonymous functions
struct NewList {
    new: fn<T>() -> List<T>,
}
let new_list = NewList {
    new: fn<T>() -> List<T> {
        List::new()
    },
};
let new = new_list.new;
assert(f"{new()}" == "[]");
}

Types

#![allow(unused)]
fn main() {
// unit - rebo's void type
let unit = ();
assert(unit == ());

// strings
// string lengths
let s = "uiae";
assert(s.len_utf8() == 4);
assert(s.len_utf16() == 4);
assert(s.len_utf32() == 4);
assert(s.len_grapheme_clusters() == 4);
assert(s.len_legacy_grapheme_clusters() == 4);

// format strings
let foo = 4;
assert(f"{foo * 10 + 2} is the answer" == "42 is the answer");
assert(f"{1337:#8x}" == "   0x539");
assert(f"{"uiae":?}" == "\"uiae\"");

// string slicing
let s = "abcdef";
assert(s.slice(1) == "bcdef");
assert(s.slice(-1) == "f");
assert(s.slice(1, 3) == "bc");
assert(s.slice(0, -1) == "abcde");
assert(s.slice(1, -1) == "bcde");
assert(s.slice(-2, -1) == "e");
let s = "αβγδεζ";
assert(s.slice(1) == "βγδεζ");
assert(s.slice(-1) == "ζ");
assert(s.slice(1, 3) == "βγ");
assert(s.slice(0, -1) == "αβγδε");
assert(s.slice(1, -1) == "βγδε");
assert(s.slice(-2, -1) == "ε");
}

Statics

#![allow(unused)]
fn main() {
assert(MY_STATIC == 42);

static mut MY_STATIC = 42;

MY_STATIC = 1337;
use_static();
assert(MY_STATIC == 21);

fn use_static() {
    assert(MY_STATIC == 1337);
    MY_STATIC = 21;
}
}

Structs

#![allow(unused)]
fn main() {
struct Foo {
    a: int,
    b: string,
}

let mut foo = Foo { a: 1337, b: "uiae" };
assert(f"{foo}" == "Foo { a: 1337, b: uiae }");

let foo2 = Foo { a: 1337, b: "dtrn" };
assert(foo != foo2);

foo.b = "dtrn";
assert(foo == foo2);

struct Inner { x: int }
struct Outer { inner: Inner }

let mut outer = Outer {
    inner: Inner {
        x: 1337,
    }
};

assert(outer.inner.x == 1337);

outer.inner.x = 420;
assert(outer.inner.x == 420);

outer.inner = Inner { x: 69 };
assert(outer.inner.x == 69);

let mut other_inner = Inner { x: 42 };
outer.inner = other_inner;
assert(outer.inner.x == 42);

other_inner.x = 21;
assert(outer.inner.x == 21);
}

Methods

#![allow(unused)]
fn main() {
struct Foo {
    a: int,
    b: string,
}

impl Foo {
    fn new(a: int, b: string) -> Foo {
        Foo { a: a, b: b }
    }
    fn a(self) -> int {
        self.a
    }
    fn b(self) -> string {
        self.b
    }
}
assert(f"{Foo::new(1337, "uiae"):?}" == "Foo { a: 1337, b: \"uiae\" }");
let foo = Foo::new(42, "uiae");
assert(foo.a() == 42 && foo.b() == "uiae");
assert(Foo::a(foo) == 42 && Foo::b(foo) == "uiae");

struct Something {}
impl Something {
    fn returns_self(self) -> Something {
        self
    }
}
let foo = Something {};
assert(foo.returns_self().returns_self() == foo);
}

Enums

#![allow(unused)]
fn main() {
// enums
enum Value {
    Unit,
    Integer(int),
    Float(float),
}
// enum matching
fn value_to_string(value: Value) -> string {
    match value {
        Value::Unit => "unit",
        Value::Integer(i) => f"integer: {i}",
        _ => "something else",
    }
}

let unit = Value::Unit;
let i = Value::Integer(1337);
let f = Value::Float(42.);
assert(f"{unit}, {i}, {f}" == "Unit, Integer(1337), Float(42)");
assert(value_to_string(unit) == "unit");
assert(value_to_string(i) == "integer: 1337");
assert(value_to_string(f) == "something else");
}

Generics

#![allow(unused)]
fn main() {
// generics
struct GenericFoo<T> {
    bar: GenericBar<T>,
}
struct GenericBar<U> {
    t: U,
}
let foo = GenericFoo { bar: GenericBar { t: 1337 } };
assert(foo.bar.t + 1 == 1338);

fn id<T>(t: T) -> T { t }
assert(id(id(1337)) == 1337);
fn id1<U, V>(u: U, v: V) -> V { v }
fn id2<T>(t: T) -> T { id1(42, t) }
assert(id2(1337) == 1337);

// defined in stdlib:
// enum Option<T> {
//     Some(T),
//     None,
// }
let a = Option::Some(1337);
let b = Option::Some(42);
let c = Option::None;
assert(a.unwrap() + b.unwrap() + c.unwrap_or(21) == 1400);

let a = Option::Some(42);
let b: Option<Option<string>> = Option::Some(Option::Some("uiae"));
assert(f"{a}, {b}, {b.unwrap().unwrap()}" == "Some(42), Some(Some(uiae)), uiae");

// defined in stdlib:
// enum Result<T, E> {
//     Ok(T),
//     Err(E),
// }
let a = Result::Err("error");
let b = Result::Err("error");
assert(a == b);
let a = Result::Ok(1337);
let b = Result::Ok(42);
assert(f"{a}, {b}, {a.unwrap() + b.unwrap()}" == "Ok(1337), Ok(42), 1379");
}

Includes

#![allow(unused)]
fn main() {
// filename: test-include.re

static MY_INCLUDED_STATIC = "uiae";

fn included_fn() -> int {
    MY_STATIC
}

static DEFINED_IN_INCLUDE = 1;
fn defined_in_include() -> int { 2 };

assert_eq(5, DEFINED_AFTER_INCLUDE);
assert_eq(7, defined_after_include());

"this is the inclusion result"
}
#![allow(unused)]
fn main() {
static mut MY_STATIC = 42;

assert_eq(1, DEFINED_IN_INCLUDE);
assert_eq(2, defined_in_include());

let value = include "test-include.re";
assert_eq(value, "this is the inclusion result");

static DEFINED_AFTER_INCLUDE = 5;

fn defined_after_include() -> int { 7 }

assert(MY_INCLUDED_STATIC == "uiae");
assert(included_fn() == 42);
}

Collections

List<T>

#![allow(unused)]
fn main() {
let list = List::new();
assert(f"{list}" == "[]");

list.push(1337);
list.push(42);

assert(f"{list}" == "[1337, 42]");
assert(list.get(0) == Option::Some(1337));
assert(list.get(1).unwrap() == 42);
assert(list.get(2) == Option::None);

let list2 = List::of(1, 2, 3);
assert(f"{list2}" == "[1, 2, 3]");

list.set(1, 21).unwrap();
assert(list.get(1).unwrap() == 21);
}

Map<K, V>

#![allow(unused)]
fn main() {
let mut map = Map::new();

map.insert("a", 1337);
map.insert("b", -42);

assert(f"{map}" == "{a: 1337, b: -42}");
assert(f"{map.keys()}" == "[a, b]");
assert(f"{map.values()}" == "[1337, -42]");

assert(map.get("a") == Option::Some(1337));
assert(map.remove("b") == Option::Some(-42));
assert(map.get("b") == Option::None);
}

Set<T>

#![allow(unused)]
fn main() {
let mut set = Set::new();

set.insert(1337);
set.insert(42);

assert(set.len() == 2);

assert(set.insert(1337) == false);
assert(set.len() == 2);

assert(set.contains(42));
assert(set.remove(42));
assert(!set.contains(42));
}