# Openbirch Rust

Openbirch rewritten in rust.

Very messy and bad, im gonna start the umpteenth rewrite soon.

# Features

- [x] Constants (numbers)
  - [x] Addition
  - [x] Subtraction
  - [x] Multiplication
  - [x] Division
  - [x] Arbitrary float precision
- [ ] Sets (or lists, arrays, etc)
  - [x] Definition
  - [x] Multiplication with constants
  - [x] Concattenation
  - [ ] Index operator
- [x] Functions
  - [x] Native functions
  - [x] User defined functions
  - [x] Currying
  - [x] Closures
  - [x] Calling
- [x] Scopes
  - [x] Shadowing variables
- [ ] Ranges
  - [x] Defining ranges (`1..5` is a range of `[1..5)`)
  - [ ] Iterating ranges
  - [x] Evaluating ranges (very hacky, but defining a Set of length 1 with a range expands said range into the set, eg `[1..3]` becomes `[1,2]`)
- [ ] Loops
  - [x] Infinite loops (`loop ... end` block)
  - [ ] For loops (`for x in y ... end` block)
  - [x] Break keyword
- [ ] Match expression
- [x] If expression
  - [x] If expression
  - [x] If-else expression

## Wanted

- [ ] More concise syntax, i dont like how it is right now
- [ ] Fix stack overflow with recursive functions (internally convert to loops)
    - [ ] Tail call optimization
- [ ] Generally implement more 
- [ ] Implicit multiplication (may not be possible to implement non-ambiguously)
- [ ] Differentiation
- [ ] Integration
- [ ] Better error handling (i was lazy and skimped on errors, bad move)

# Syntax

Every Openbirch program is made up of 0 or more *expressions*.

An expression is anything from a binary operation like `2+2` to function calls `f(x)` and
even assignments `x := 5`.

All expressions return a value. For expressions that "dont" they return an `Empty`, which cant
be used for anything and will error if used in other operations (eg. `2+{Empty}`).

## Definition

Variables can be defined with a `define` expression.

```
x := 5 # define x as 5

print(x+x) # prints 10
```

As openbirch is a symbolic language variables can be used before assignment

```
y := x^2

print(y) # prints x^2

x := 5

print(y) # prints 25
```

### Scopes

Some expressions define a *scope*, where all defined variables inside will be deleted once the scope ends.

Expressions that create a scope include, but are not limited to,
- If-else
- Loop
- Functions
- Closures

If a variable is defined outside a scope it will be *shadowed* inside the scope. What this means
is that inside the scope the variable will have the shadowed value, while outside the scope it
will have the previous value.

```
x := 5 # Global scope

print(x) # prints 5

if true then    # Define a new scope
    x := 5000   # Shadow the value of x
    print(x)    # prints 5000
end             # Scope ends here

print(x) # prints 5
```

### Assignment

Since you may want to change the value of a variable outside the current scope you can use an `assignment` rather than a definition.

```
x := 5

if true then
    x <- 500 # Assign to x rather than defining it and shadowing it
end

print(x) # prints 500
```

### Set assignment

You can unpack a set by assigning it to another set, eg

```
[x,y] := [5,6]

print(x) # 5
print(y) # 6
```

The sets must have the same length, otherwise the expression will error.

This is useful for functions such as `head`

## If-else

If expressions are defined as such

```
if {condition} then
    # 0 or more statements
end
```

and if-else as

```
if {condition} then
    # 0 or more statements
else 
    {expression}
end
```

Since `if` is an expression the body of the else branch can be another if expression, in which
case only 1 `end` keyword is needed.

```
if {condition} then
    # 0 or more statements
else if {condition} then
    # 0 or more statements
end
```

Since all expressions evaluate to a value the value of the if expression is the resulting value
of the last statement in the chosen branch, eg.

```
x := if true then
    # Evaluate some expressions
    2+2
    f(x)
    y := f(f(x))
    # Last expression is returned
    5
else
    0
end

print(x) # prints 5
```

if a branch is empty then `Empty` is returned.

## Loops

Currently the only type of loop is an infinite loop.

```
loop
    # This will repeat forever
end
```

In order to avoid the system hanging you can use the `break` keyword to stop a loop.

```
x := 5

loop
    if x = 0 then
        break
    end

    x <- x-1
end
```

## Functions

Functions in openbirch allow you to define operations on a set of inputs. Functions are a first class type and can be defined as a set of arguments and a body.

`args -> body`

Heres a few examples:

```
# A function that takes 1 argument and returns the argument multiplied by 2
f := x -> x*2

f(4) # 8
```

If you want to use multiple arguments then supply a set as the argument

```
# A function that takes 2 arguments and adds them together
f := [x,y] -> x+y

f(2,3) # 5
```

Assigning a function to a name is the most common way of using them. This can be done in 2 ways

```
# Define f with a value that is a function
f := x -> x*2

# This does the same, but is more intuitive for people from a math background
f(x) := x*2
```

### Currying

Functions can return other functions. This creates a *closure*, that captures the variables passed to it.

```
add := x -> y -> x+y

add(2)(3) # this evaluates to 5

add2 := add(2) # This returns a closure where x is defined as 2

print(add2) # this prints ( (x: 2) => ([y] -> x+y))
            # What this shows is a closure that captures "x: 2",
            # where inside is another function that defines y

add2(5) # this is the same as add(2)(5) and returns 7.
```

### Built-in functions

A few functions are defined by default. They provide additional functionality
that has yet to be implemented into the language, such as syntax for indexing sets
and getting their length.

- `length(Set)` Returns the length of the given set
- `get(Set,Constant)` Returns the value at the given index in the set
- `head(Set)` Returns another Set consisting of `[head, [tail]]` where `head` is the first element in the set, and `tail` is all elements except the first element.
- `map(Function, Set)` Returns a new set where every value has had the function applied to it.
- `unset(String)` Undefines the given variable
- `set_float_precision(Constant)` Sets the number of bits used for numbers. Default is 128bits
- `get_float_precision(<optional Constant>)` Returns the current float precision. If a constant is given as the argument it will return the number of bits used for that constant.

# Running

## Linux

```sh
$ cargo run
```

### Nixos

```sh
$ nix develop
$ cargo run
```

## Windows

idfk, havent used that shit in years, but probably just `cargo run`.

## MacOS

never used, but again probably `cargo run`
