If you use libraries, be aware that they run as a seperate process that might not exit with mers!
This means that, after running a script 40-50 times (which can happen more quickly than you might realize),
you might find 40-50 random processes just running and possibly maxing our your cpu.
So if you use libraries (recommendation: don't, the implementation is pretty bad anyway. just use any other language), make sure to kill those processes once you're done
until I figure out how to make that happen automatically.
(I believe the issue happens when closing the window from the GUI library, which crashes mers, leaving the http library process running)
(other than that, the language is pretty usable, i promise...)
To achieve this, each 'type' is actually a list of single types that are all valid in the given context. All types must be handeled or the compiler will show an error.
To avoid having to type so much, types are inferred almost everywhere. It is enough to say `x = if condition "now it's a string" else []`. The compiler will give x the type `string/[]` automatically.
Since there are no classes or structs, tuples are also widely used, for examples, `[[]/int string string]` is returned by the builtin run_command() on success. It represents the exit code (if it exists), stdout and stderr.
The compiler checks your program. It will guarantee type-safety. If a variable has type `int/float/bool`, it cannot be used in the add() function, because you can't add bools.
for simplicity, i will assume you have the executable in your path and it is named mers. Since this probably isn't the case, just know that `mers` can be replaced with `cargo run --release` in all of the following commands.
If you compiled mers in debug mode, it will print a lot of debugging information.
Somewhere in mers' output, you will see a line with five '-' characters: ` - - - - -`. This is where your program starts running. The second time you see this line is where your program finished. After this, You will see how long your program took to run and its output.
### Hello, World!
Since this is likely your first time using mers, let's write a hello world program:
println("Hello, World!")
If you're familiar with other programming languages, this is probably what you expected. Running it prints Hello, World! between the two five-dash-lines.
The `"` character starts/ends a string literal. This creates a value of type `string` which is then passed to the `println()` function, which writes the string to the programs stdout.
### Hello, World?
But what if we didn't print anything?
"Hello, World?"
Running this should show Hello, World? as the program's output. This is because the output of a code block in mers is always the output of its last statement. Since we only have one statement, its output is the entire program's output.
### Hello, Variable!
Variables in mers don't need to be declared explicitly, you can just assign a value to any variable:
x = "Hello, Variable!"
println(x)
x
Now we have our text between the five-dash-lines AND in the program output. Amazing!
### User Input
The builtin `read_line()` function reads a line from stdin. It can be used to get input from the person running your program:
println("What is your name?")
name = read_line()
print("Hello, ")
println(name)
`print()` prints the given string to stdout, but doesn't insert the linebreak `\n` character. This way, "Hello, " and the user's name will stay on the same line.
### Format
`format()` is a builtin function that takes a string (called the format string) and any number of further arguments. The pattern `{n}` anywhere in the format string will be replaced with the n-th argument, not counting the format string.
println("What is your name?")
name = read_line()
println(format("Hello, {0}! How was your day?" name))
### alternative syntax for the first argument
If function(a b) is valid, you can also use a.function(b). This does exactly the same thing, because a.function(args) inserts a as the first argument, moving all other args back by one.
This can make reading `println(format(...))` statements a lot more enjoyable:
println("Hello, {0}! How was your day?".format(name))
However, this can also be overused:
"Hello, {0}! How was your day?".format(name).println()
I would consider this to be less readable because `println()`, which is the one and only thing this line was written to achieve, is now the last thing in the line. Readers need to read the entire line before realizing it's "just" a print statement. However, this is of course personal preference.
### A simple counter
Let's build a counter app: We start at 0. If the user types '+', we increment the counter by one. If they type '-', we decrement it. If they type anything else, we print the current count in a status message.
The first thing we will need for this is a loop:
while {
println("...")
}
Running this should spam your terminal with '...'.
Now let's add a counter variable and read user input:
counter = 0
while {
input = read_line()
if input.eq("+") {
counter = counter.add(1)
} else if input.eq("-") {
counter = counter.sub(1)
} else {
println("The counter is currently at {0}. Type + or - to change it.".format(counter))
This works because most {}s are optional in mers. The parser just parses a "statement", which *can* be a block.
A block starts at {, can contain any number of statements, and ends with a }. This is why we can use {} in if statements, function bodies, and many other locations. But if we only need to do one thing, we don't need to put it in a block.
In fact, `fn difference(a int/float b int/float) if a.gt(b) a.sub(b) else b.sub(a)` is completely valid in mers (gt = "greater than").
Because a value of type int matches, we now break with "res: 51". For more complicated examples, using `[i]` instead of just `i` is recommended because `[i]` matches even if `i` doesn't.
Since `get()` can fail, it returns `[]/[t]` where t is the type of elements in the list. To avoid handling the `[]` case, the `assume1()` builtin takes a `[]/[t]` and returns `t`. If the value is `[]`, it will cause a crash.
- An empty tuple `[]`, `false`, and any enum member `any_enum_here(any_value)` will not match.
- A one-length tuple `[v]` will match with `v`, `true` will match with `true`.
- A tuple with len >= 2 is considered invalid: It cannot be used for matching because it might lead to accidental matches and could cause confusion. If you try to use this, you will get an error from the compiler.
+ the tuple type is written as any number of types separated by whitespace(s), enclosed in square brackets: [int string].
+ tuple values are created by putting any number of statements in square brackets: ["hello" "world" 12 -0.2 false].
- list
+ list types are written as a single type enclosed in square brackets: [string]. TODO! this will likely change to [string ...] or something similar to allow 1-long tuples in function args.
+ list values are created by putting any number of statements in square brackets, prefixing the closing bracket with ...: ["hello" "mers" "world" ...].
+ function types are written as `fn(args) out_type`. (TODO! implement this)
+ function values are created using the `(first_arg_name first_arg_type second_arg_name second_arg_type) statement` syntax: `anonymous_power_function = (a int b int) a.pow(b)`.
+ to run anonymous functions, use the run() builtin: `anonymous_power_function.run(4 2)` evaluates to `16`.
+ note: functions are defined using the `fn name(args) statement` syntax and are different from anonymous functions because they aren't values and can be run directly: `fn power_function(a int b int) a.pow(b)` => `power_function(4 2)` => `16`
+ a special type returned by the thread builtin. It is similar to JavaScript promises and can be awaited to get the value once it has finished computing. Reading the thread example is probably the best way to see how this works.