mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-10 05:43:53 +01:00
updated readme. again.
This commit is contained in:
parent
29bc366439
commit
16258c7a0a
289
README.md
289
README.md
@ -1,27 +1,278 @@
|
||||
# mers 
|
||||
|
||||
Mers is an experimental programming language inspired by high-level and scripting languages, but with error handling inspired by rust.
|
||||
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?
|
||||
|
||||
mers has...
|
||||
### it doesn't crash
|
||||
|
||||
- the type-safety of statically typed languages
|
||||
+ mers will not crash unless you `exit()` with a nonzero exit code or write flawed assumptions using the builtin `.assume*()` functions (Rust's `unwrap` or `expect`)
|
||||
- the flexibility of dynamically typed languages
|
||||
+ `x := if condition() { "my string" } else { 12 } // <- this is valid`
|
||||
- "correctness" (this is subjective and I'll be happy to discuss some of these decisions with people)
|
||||
+ there is no `null` / `nil`
|
||||
+ all references are explicit: if you pass a list by value, the original list will *never* be modified in any way.
|
||||
+ errors are normal values! (no special treatment)
|
||||
- a flexible type system to easily represent these errors and any complex structure including recursive types:
|
||||
+ nothing: `[]` (an empty tuple)
|
||||
+ a string: `string`
|
||||
+ two strings: `[string string]` (a tuple)
|
||||
+ many strings: `[string ...]` (a list)
|
||||
+ Either a string or nothing (Rust's `Option<String>`): `string/[]`
|
||||
+ Either an int or an error: (Rust's `Result<isize, String>`): `int/string` (better: `int/Err(string)`)
|
||||
- compile-time execution through (explicit) macro syntax: `!(mers {<code>})` or `!(mers "file")`
|
||||
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?
|
||||
|
||||
@ -37,7 +288,7 @@ Now, create a new text file (or choose one from the examples) and run it: `mers
|
||||
|
||||
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 change of anyone encountering this is low, but it's something to be aware of.
|
||||
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
|
||||
|
Binary file not shown.
Binary file not shown.
@ -96,21 +96,20 @@ impl RFunction {
|
||||
pub fn out_by_map(&self, input_types: &Vec<VType>, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
// NOTE: This can ONLY use self.out_map, because it's used by the VSingleType.fits_in method.
|
||||
let mut empty = true;
|
||||
let out = self
|
||||
.out_map
|
||||
.iter()
|
||||
.fold(VType::empty(), |mut t, (fn_in, fn_out)| {
|
||||
if fn_in.len() == (input_types.len())
|
||||
&& fn_in
|
||||
.iter()
|
||||
.zip(input_types.iter())
|
||||
.all(|(fn_in, arg)| arg.fits_in(fn_in, info).is_empty())
|
||||
{
|
||||
empty = false;
|
||||
t.add_typesr(fn_out, info);
|
||||
}
|
||||
t
|
||||
});
|
||||
let out =
|
||||
self.out_map
|
||||
.iter()
|
||||
.fold(VType::empty(), |mut t, (fn_in, fn_out)| {
|
||||
if fn_in.len() == (input_types.len())
|
||||
&& fn_in.iter().zip(input_types.iter()).all(|(fn_in, arg)| {
|
||||
arg.types.iter().any(|t| t.fits_in_type(fn_in, info))
|
||||
})
|
||||
{
|
||||
empty = false;
|
||||
t.add_typesr(fn_out, info);
|
||||
}
|
||||
t
|
||||
});
|
||||
if empty {
|
||||
None
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user