0.1.0 May 14, 2020

#15 in #eventually

BSD-3-Clause

83KB
2.5K SLoC

hibiscus

Hibiscus is an embeddable, statically typed scripting language.

Rust

Progress

  • Milestone 1
    • Minimum viable programming language (untyped, few features)
    • Syntax highlighting in editor
  • Milestone 2
    • Type system added
    • Signature types
    • Struct types
  • Milestone 3 (v0.1 release)
    • Multithreaded VM (minimum viable)
    • Iterators (including for loops, array filtering/mapping/etc)
    • User-denotable exceptions
    • Tuples
    • Sum types
    • Pattern matching
    • Modules (including OCaml-style functors)
    • Full stdlib
  • Milestone 4 (v0.2 release)
    • LSP server

Samples

Some of the below samples are out of date with where the language will eventually move, especially the ones about mutability. Runnable samples are located in the samples/ folder.

hello_world.bi

print("Hello, world!")

Hello, world!


fibonacci.bi

let first_val = 0
let second_val = 1

-- The last expression of a function is its return value.
fun fibonacci(n: int) -> int =
    -- multiple statements can be separated with semicolons
    print(f"finding fibonacci for {n}");

    -- you can return from functions early
    if n < 0 then
        return 0
    end;

    -- the last expression is the function's return value
    if n = 0 then
        first_val
    else if n = 1 then
        second_val
    else
        fib(n - 2) + fib(n - 1)
    end
end

print(fibonacci(3))

2


objects.bi

-- literal object types don't have to be defined beforehand
let sample_plants = {
    evergreen: [
        "fir",
        "redwood",
    ],
    deciduous: [
        "maple",
        "apple",
    ],
    flowering: "lavender",
}

print(signature(sample_plants));
print(sample_plants.evergreen[0])

{evergreen: list<string>, deciduous: list<string>, flowering: string} fir


signature_types.bi

-- signature types are compared structurally, and accept any object with
-- matching fields
sig Gem = {
    cut: string,
    facet: int,
    powers: list<string>,
    ... -- the trailing ... tells Hibiscus to allow extra fields
}

-- implicit () return type if none is given
fun print_gem(gem: Gem) =
    let cut = gem.cut;
    let facet = gem.facet;
    let powers = ", ".join(gem.powers.map(fun(power) => f"\"{power}\""));
    print(f"\{cut: \"{cut}\", facet: \"{gem.facet}\", powers: [{powers}]\}")
end

-- signature types allow any object with exactly identical fields
-- if the signature ends with `...`, it also allows extra fields
print_gem({
    cut: "8XM",
    facet: 5,
    powers: ["spin dash", "electro-whip"],
    extra_field: "whatever"
})

{cut: "8XM", facet: 5, powers: ["spin dash"]}


methods.bi

-- signatures can also require methods
sig PrintableId = {
    id_number: int
} with
    -- commas are used to separate declarations
    describe_self(self) -> string,
    set_id_number(mutable self, new_id: int) -> ()
end

-- if this was declared with 'let my_id' instead of 'mutable my_id', the call to
-- 'my_id.set_id_number(6)' would be an error
mutable my_id = {
    id_number: 5
} with
    describe_self(self) -> string =
        f"{self.id_number}"
    end

    -- fields can only be updated when 'self' is mutable
    set_id_number(mutable self, new_id: int) =
        self.id_number <- new_id
    end
end

print(my_id.describe_self());
my_id.set_id_number(6);
print(my_id.describe_self())

5 6


union_types.bi

sig MySig = {
    name: string
}

type UnionType =
    | Tagged MySig
    | AlsoTagged {name: string, id: int} -- types can be defined inline

-- union cases are also types
fun get_tagged_name(item: Tagged MySig) -> string =
    item.name
end

-- union types need to be pattern matched
fun get_name(item: UnionType) -> string =
    match item with
    | Tagged tagged => get_tagged_name(tagged)
    | AlsoTagged also_tagged => also_tagged.name
end

let item = Tagged {name: "my item"};
print(get_name(item));

let item2 = {name: "my item2"};
print(get_name(AlsoTagged item2))

my item my item2


structs.bi

-- structs are nominally typed, so only an instance of MyItem can be passed to a
-- function that expects a MyItem.
struct MyItem = {
    name: string,
    id: int,
    _secret_field: string -- fields that start with '_' are private
} with
    get_name(self) -> string =
        self.name
    end

    set_name(mutable self, new_name: string) =
        self._secret_method();
        self.name <- new_name
    end

    -- methods that start with '_' can only be accessed by other methods
    _secret_method(self) =
        print("secret method!")
    end

    -- no 'self' parameter means static method
    default_name() -> string = "standard" end

    new(name: string, id: int) -> MyItem =
        -- the struct's name can be used as a function to create a new struct
        -- from the given object
        -- if there are any private fields, structs can only be created in a
        -- static method
        MyItem {
            name,
            id,
            _secret_field: "secret"
        }
    end
end

-- mutable items can be local, arguments, or self, but cannot be assigned to
-- fields
mutable my_item = MyItem.new("name", 5)

-- unless overridden, all values have a default conversion to string
print(my_item);
my_item.set_name(MyItem.default_name());
print(my_item)

MyItem { name: "name", id: 5, _secret_field: "secret"} MyItem { name: "standard", id: 5, _secret_field: "secret"}

Dependencies

~1MB
~20K SLoC