# 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()` 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`