mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-10 22:37:46 +01:00
303 lines
7.1 KiB
Markdown
Executable File
303 lines
7.1 KiB
Markdown
Executable File
# mers 
|
|
|
|
Mers is a simple and reliable programming language.
|
|
|
|
While mers can already be used right now,
|
|
many things about it will still change -
|
|
at least until 1.0.0 is released.
|
|
For serious projects, this probably
|
|
isn't the language you should be using.
|
|
|
|
## Why mers?
|
|
|
|
### it doesn't crash
|
|
|
|
If your program starts, **it won't crash**.
|
|
The only exceptions to this are
|
|
the builtin `exit()` function, which can exit with a nonzero exit code
|
|
and the builtin `assume` functions, similar to Rust's `unwrap()`.
|
|
|
|
This is because **errors in mers are values**,
|
|
and the compiler forces you to handle them.
|
|
|
|
### type system
|
|
|
|
In mers, a type can consist of multiple types:
|
|
|
|
```
|
|
// bool
|
|
half := true
|
|
// float/int
|
|
number := if half { 0.5 } else { 1 }
|
|
|
|
// some math functions that work with int and float
|
|
fn absolute_value(num int/float) {
|
|
if num >= 0 { num } else { 0 - num }
|
|
}
|
|
fn difference(a int/float, b int/float) {
|
|
absolute_value(a - b)
|
|
}
|
|
|
|
debug(difference(0.2, 2)) // float: 1.8
|
|
// although difference can work with floats, since we only give it ints, it also returns an int.
|
|
// this is known at compile time.
|
|
debug(difference(20, 5)) // int: 15
|
|
```
|
|
|
|
With this, you can write code like you would in dynamically typed languages:
|
|
|
|
```
|
|
user_input := if true { "some text input" } else { 19 }
|
|
```
|
|
|
|
But you get the compile-time checks you love from statically typed ones:
|
|
|
|
```
|
|
if user_input >= min_age {
|
|
println("welcome!")
|
|
}
|
|
```
|
|
|
|
Because `user_input` could be a string, the code above won't compile.
|
|
|
|
### it's simple
|
|
|
|
Almost everything in mers is built around this type system.
|
|
This eliminates many concepts you may be familiar with:
|
|
|
|
#### instead of null or exceptions, just add `[]` or the error(s) to the return type
|
|
|
|
```
|
|
// returns `[]` instead of dividing by zero
|
|
fn div_checked(a int, b int) -> int/[] {
|
|
if b != 0 {
|
|
a / b
|
|
} else {
|
|
[]
|
|
}
|
|
}
|
|
|
|
// returns the file content as a string.
|
|
// if the file can't be read, returns IoError and an error message.
|
|
// if the file's contents weren't Utf8, return NotUtf8 and the file's bytes.
|
|
fn load_file_as_string(path string) -> string/IoError(string)/NotUtf8([int ...]) {}
|
|
```
|
|
|
|
#### structs are nothing more than tuples
|
|
|
|
```
|
|
// define our type named Person
|
|
type Person [int, string]
|
|
|
|
// function to get (but not set) the person's birth year
|
|
fn birth_year(self Person) self.0
|
|
|
|
// function to get or set the person's name
|
|
fn name(self Person/&Person) self.1
|
|
|
|
person := [1999, "Ryan"]
|
|
|
|
// Get the values
|
|
person.name() // Ryan
|
|
person.birth_year() // 1999
|
|
|
|
// Change the name
|
|
&person.name() = "James"
|
|
// or: name(&person) = "James"
|
|
person.name() // James
|
|
|
|
// This doesn't compile:
|
|
// &person.birth_year() = 1998
|
|
```
|
|
|
|
#### enums are already part of the type system
|
|
|
|
```
|
|
type DirectoryEntry File(string)/Dir(string)/Link(DirectoryEntry)
|
|
```
|
|
|
|
#### return and break
|
|
|
|
Most people agree that `goto` statements are usually a bad idea.
|
|
But `return` and `break` have similar issues, although less severe:
|
|
|
|
**they create weird control flow:**
|
|
|
|
Maybe you want to see how long a function takes to execute, so
|
|
you add a `println` to the end - just to see absolutely no output.
|
|
Now you might think the function is stuck in an infinite loop somewhere,
|
|
although it just returned before it ever got to your println.
|
|
This can be annoying. If we remove `return` statements,
|
|
it becomes way easier to tell when code will or won't be executed.
|
|
|
|
**return is exclusive to functions, break to loops**
|
|
|
|
With mers, whenever possible, concepts in the language should work on *statements*.
|
|
|
|
An example of this are type annotations:
|
|
|
|
```
|
|
fn half(num int) -> int { num / 2 }
|
|
```
|
|
|
|
The `-> int` indicates that this function returns an int.
|
|
Actually, it indicates that the statement following it returns an int.
|
|
This obviously means the function will also return an int,
|
|
but since this syntax isn't specifically made for functions,
|
|
we can use it anywhere we want:
|
|
|
|
```
|
|
num := -> int { 4 + 5 }
|
|
half(-> int { double * 2})
|
|
```
|
|
|
|
**So how do we return values then?**
|
|
Simple!
|
|
|
|
```
|
|
// a function is just another statement
|
|
fn return_int() 4 + 5
|
|
|
|
// a block returns whatever its last statement returns,
|
|
fn return_string() {
|
|
a := "start"
|
|
b := "end"
|
|
a + " " + b
|
|
}
|
|
|
|
// this returns string/[], because there is no else
|
|
if condition() {
|
|
"some value"
|
|
}
|
|
|
|
// this returns string/int
|
|
if condition() {
|
|
"some value"
|
|
} else {
|
|
12
|
|
}
|
|
```
|
|
|
|
Most returns should be relatively intuitive,
|
|
although some special ones (like loops)
|
|
use matching (see docs/intro)
|
|
|
|
### it has references
|
|
|
|
To explain why this is necessary,
|
|
let's look at examples from other languages.
|
|
|
|
Here is some JavaScript:
|
|
|
|
```js
|
|
function doSmth(list) {
|
|
list[0] = 0
|
|
}
|
|
function modify(num) {
|
|
num = 0
|
|
}
|
|
list = [1]
|
|
num = 1
|
|
doSmth(list)
|
|
modify(num)
|
|
console.log(list) // [ 0 ]
|
|
console.log(num) // 1
|
|
```
|
|
|
|
The same thing in Go:
|
|
|
|
```go
|
|
package main
|
|
import "fmt"
|
|
|
|
func doSmth(list []int) {
|
|
list[0] = 0
|
|
}
|
|
func modify(num int) {
|
|
num = 0
|
|
}
|
|
func main() {
|
|
list := []int{1}
|
|
num := 1
|
|
doSmth(list)
|
|
modify(num)
|
|
fmt.Println(list) // [0]
|
|
fmt.Println(num) // 1
|
|
}
|
|
```
|
|
|
|
In both cases, the function was able to modify the list's contents,
|
|
but unable to change the number to a different value.
|
|
This is not just inconsistent, it's also not always what you wanted to do:
|
|
|
|
- i passed the list by value, how could the function change the inner value?
|
|
- the function is called `modify`, why can't it modify the value?
|
|
+ In Go, we could use references to make this work - but that just makes the list example even more annoying, since we don't need a reference to change the inner value.
|
|
|
|
So, let's look at mers:
|
|
|
|
```
|
|
fn doSmth(list [int ...]) {
|
|
&list.get(0).assume1() = 0
|
|
}
|
|
fn modify(num &int) {
|
|
num = 0
|
|
}
|
|
list := [1 ...]
|
|
num := 1
|
|
doSmth(list)
|
|
modify(&num)
|
|
debug(list) // [1 ...]
|
|
debug(num) // 0
|
|
```
|
|
|
|
The list is unchanged, but the number was changed by `modify`,
|
|
because it was explicitly passed as a reference `&int`.
|
|
**Unless you pass a reference, the value you passed will not be changed.**
|
|
|
|
### compile-time execution via macros
|
|
|
|
```
|
|
// at compile-time, runs primes.mers to find the first few prime numbers
|
|
primes := !(mers "primes.mers")
|
|
|
|
// at compile-time, runs the code to create a string
|
|
// containing 256 '-'s
|
|
long_string := !(mers {
|
|
str := "-"
|
|
loop {
|
|
str = str + str
|
|
if str.len() >= 256 {
|
|
str
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
## How mers?
|
|
|
|
There are prebuilt binaries in `build_scripts/`.
|
|
|
|
Mers is written in Rust. If you have `cargo`, you can also use the build script in `build_scripts/` to build it yourself.
|
|
|
|
Now, create a new text file (or choose one from the examples) and run it: `mers <file>`.
|
|
|
|
## Known Issues (only major ones)
|
|
|
|
### Multithreading
|
|
|
|
If a function is called from two threads, all local variables of that function are shared.
|
|
This doesn't affect builtin functions, and since functions usually don't take long to execute,
|
|
the chance of anyone encountering this is low, but it's something to be aware of.
|
|
It's a simple fix in theory, but a lot of work to implement, which is why the bug isn't fixed yet.
|
|
|
|
## Docs
|
|
|
|
[intro](docs/intro.md)
|
|
|
|
[syntax cheat sheet](docs/syntax_cheat_sheet.md)
|
|
|
|
[builtins](docs/builtins.md)
|
|
|
|
[statements](docs/statements.md)
|